diff options
| author | Indoteknik . <it@fixcomart.co.id> | 2025-08-29 08:18:41 +0700 |
|---|---|---|
| committer | Indoteknik . <it@fixcomart.co.id> | 2025-08-29 08:18:41 +0700 |
| commit | bec429267fd4baca3a914faefba974f18c2695cb (patch) | |
| tree | 37739237e2d9650b866423f844709c68c2171f28 | |
| parent | 83759c763cbde2b179472446231bca007c153449 (diff) | |
| parent | 0112ac064a7484685119cf9371ffbea32de6fd59 (diff) | |
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into pum-v2
26 files changed, 659 insertions, 127 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 39995b21..4b15c0a6 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -8,8 +8,9 @@ 'author': 'Rafi Zadanly', 'website': '', 'images': ['assets/favicon.ico'], - 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur' ], + 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur'], 'data': [ + 'views/assets.xml', 'security/ir.model.access.csv', 'views/group_partner.xml', 'views/blog_post.xml', diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index b0ffd8b9..c44cad78 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -99,6 +99,12 @@ class AccountMove(models.Model): reminder_sent_date = fields.Date(string="Tanggal Reminder Terkirim") + customer_promise_date = fields.Date( + string="Janji Bayar", + help="Tanggal janji bayar dari customer setelah reminder dikirim.", + tracking=True + ) + 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': @@ -121,6 +127,39 @@ class AccountMove(models.Model): else: move.payment_date = False + def action_sync_promise_date(self): + self.ensure_one() + finance_user_ids = [688] + if self.env.user.id not in finance_user_ids: + raise UserError('Hanya Finance (Widya) yang dapat menggunakan fitur ini.') + if not self.customer_promise_date: + raise UserError("Isi Janji Bayar terlebih dahulu sebelum melakukan sinkronisasi.") + + other_invoices = self.env['account.move'].search([ + ('id', '!=', self.id), + ('partner_id', '=', self.partner_id.id), + ('invoice_date_due', '=', self.invoice_date_due), + ('move_type', '=', 'out_invoice'), + ('state', '=', 'posted'), + ('date_terima_tukar_faktur', '!=', False) + ]) + lines = [] + for inv in other_invoices: + lines.append((0, 0, {'invoice_id': inv.id, 'sync_check': True})) # default dicentang semua + + wizard = self.env['sync.promise.date.wizard'].create({ + 'invoice_id': self.id, + 'line_ids': lines, + }) + + return { + 'name': 'Sync Janji Bayar', + 'type': 'ir.actions.act_window', + 'res_model': 'sync.promise.date.wizard', + 'view_mode': 'form', + 'res_id': wizard.id, + 'target': 'new', + } def send_due_invoice_reminder(self): today = fields.Date.today() @@ -140,8 +179,9 @@ class AccountMove(models.Model): ('state', '=', 'posted'), ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), ('invoice_date_due', 'in', target_dates), - ('date_terima_tukar_faktur', '!=', False) - ]) + ('date_terima_tukar_faktur', '!=', False), + ('partner_id', 'in' , [94603]) + ], limit=5) _logger.info(f"Invoices: {invoices}") invoices = invoices.filtered( @@ -168,6 +208,24 @@ class AccountMove(models.Model): _logger.info(f"Reminder untuk {partner.name} sudah terkirim hari ini, skip.") continue + promise_dates = [inv.customer_promise_date for inv in invs if inv.customer_promise_date] + if promise_dates: + earliest_promise = min(promise_dates) # ambil janji paling awal + if today <= earliest_promise: + _logger.info( + f"Skip reminder untuk {partner.name} karena ada Janji Bayar sampai {earliest_promise}" + ) + continue + + emails = [] + # skip semua jika partner centang dont_send_reminder_inv_all + if partner.dont_send_reminder_inv_all: + _logger.info(f"Partner {partner.name} skip karena dont_send_reminder_inv_all aktif") + continue + # cek parent hanya dengan flag dont_sent_reminder_inv_parent + if not partner.dont_send_reminder_inv_parent and partner.email: + emails.append(partner.email) + # Ambil child contact yang di-checklist reminder_invoices reminder_contacts = self.env['res.partner'].search([ ('parent_id', '=', partner.id), @@ -175,12 +233,8 @@ class AccountMove(models.Model): ('email', '!=', False), ]) _logger.info(f"Email Reminder Child {reminder_contacts}") - - # if not reminder_contacts: - # _logger.info(f"Partner {partner.name} tidak memiliki email yang sudah ceklis reminder") - # continue - - emails = list(filter(None, [partner.email])) + reminder_contacts.mapped('email') + + emails += reminder_contacts.mapped('email') if reminder_contacts: _logger.info(f"Email Reminder Child {reminder_contacts}") else: @@ -194,8 +248,10 @@ class AccountMove(models.Model): _logger.info(f"Email tujuan: {email_to}") invoice_table_rows = "" + grand_total = 0 for inv in invs: days_to_due = (inv.invoice_date_due - today).days if inv.invoice_date_due else 0 + grand_total += inv.amount_total invoice_table_rows += f""" <tr> <td>{inv.partner_id.name}</td> @@ -208,6 +264,66 @@ class AccountMove(models.Model): <td>{days_to_due}</td> </tr> """ + invoice_table_footer = f""" + <tfoot> + <tr style="font-weight:bold; background-color:#f9f9f9;"> + <td colspan="5" align="right">Grand Total</td> + <td>{formatLang(self.env, grand_total, currency_obj=invs[0].currency_id)}</td> + <td colspan="2"></td> + </tr> + </tfoot> + """ + + blocking_limit = partner.blocking_stage or 0.0 + + # semua invoice tempo yang masih open + outstanding_invoices = self.env['account.move'].search([ + ('move_type', '=', 'out_invoice'), + ('state', '=', 'posted'), + ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), + ('partner_id', '=', partner.id), + ('invoice_payment_term_id.name', 'ilike', 'tempo') + ]) + + outstanding_amount = sum(outstanding_invoices.mapped('amount_total')) + + # invoice tempo yang sudah jatuh tempo + overdue_invoices = outstanding_invoices.filtered( + lambda inv: inv.invoice_date_due and inv.invoice_date_due < fields.Date.today() + ) + + overdue_amount = sum(overdue_invoices.mapped('amount_total')) + + currency = invs[0].currency_id if invs else partner.company_id.currency_id + tempo_link = 'https://indoteknik.com/my/tempo' + # tempo_link = 'http://localhost:2100/my/tempo' + + limit_info_html = f""" + <p><b>Informasi Tambahan:</b></p> + <ul style="font-size:12px; color:#333; line-height:1.4; margin:0; padding-left:0; list-style-position:inside;"> + <li>Kredit Limit Anda: {formatLang(self.env, blocking_limit, currency_obj=currency)}</li> + <li>Status Detail Tempo: {partner.property_payment_term_id.name or 'Review'}</li> + <li style="color:{'red' if (blocking_limit - outstanding_amount) < 0 else 'green'};"> + Sisa Kredit Limit: {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)} + </li> + <li style="color:red;"> + Kredit Limit Terpakai: {formatLang(self.env, outstanding_amount, currency_obj=currency)} + <span style="font-size:12px; color:#666;">({len(outstanding_invoices)} Transaksi)</span> + </li> + <li style="color:red;"> + Jatuh Tempo: {formatLang(self.env, overdue_amount, currency_obj=currency)} + <span style="font-size:12px; color:#666;">({len(overdue_invoices)} Invoice)</span> + </li> + </ul> + <p style="margin-top:10px;"> + <a href="{tempo_link or '#'}" + style="display:inline-block; padding:8px 16px; + background-color:#007bff; color:#fff; text-decoration:none; + border-radius:4px; font-size:12px;"> + Cek Selengkapnya + </a> + </p> + """ days_to_due_message = "" closing_message = "" @@ -249,13 +365,14 @@ class AccountMove(models.Model): body_html = re.sub( r"<tbody[^>]*>.*?</tbody>", - f"<tbody>{invoice_table_rows}</tbody>", + f"<tbody>{invoice_table_rows}</tbody>{invoice_table_footer}", template.body_html, flags=re.DOTALL ).replace('${object.name}', partner.name) \ .replace('${object.partner_id.name}', partner.name) \ .replace('${days_to_due_message}', days_to_due_message) \ - .replace('${closing_message}', closing_message) + .replace('${closing_message}', closing_message) \ + .replace('${limit_info_html}', limit_info_html) cc_list = [ 'finance@indoteknik.co.id', @@ -278,8 +395,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 @@ -293,7 +411,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') @@ -689,4 +806,71 @@ class AccountMove(models.Model): 'date_efaktur_exported': datetime.utcnow(), }) - return response
\ No newline at end of file + return response + +class SyncPromiseDateWizard(models.TransientModel): + _name = "sync.promise.date.wizard" + _description = "Sync Janji Bayar Wizard" + + invoice_id = fields.Many2one('account.move', string="Invoice Utama", required=True) + promise_date = fields.Date(string="Janji Bayar", related="invoice_id.customer_promise_date", readonly=True) + line_ids = fields.One2many('sync.promise.date.wizard.line', 'wizard_id', string="Invoices Terkait") + + def action_check_all(self): + for line in self.line_ids: + line.sync_check = True + return { + 'type': 'ir.actions.act_window', + 'res_model': 'sync.promise.date.wizard', + 'view_mode': 'form', + 'res_id': self.id, + 'target': 'new', + } + + def action_uncheck_all(self): + for line in self.line_ids: + line.sync_check = False + return { + 'type': 'ir.actions.act_window', + 'res_model': 'sync.promise.date.wizard', + 'view_mode': 'form', + 'res_id': self.id, + 'target': 'new', + } + + def action_confirm(self): + self.ensure_one() + selected_lines = self.line_ids.filtered(lambda l: l.sync_check) + selected_invoices = selected_lines.mapped('invoice_id') + + if not selected_invoices: + raise UserError("Tidak ada invoice dipilih untuk sinkronisasi.") + + # Update hanya invoice yang dipilih + for inv in selected_invoices: + inv.write({'customer_promise_date': self.promise_date}) + inv.message_post( + body=f"Janji Bayar {self.promise_date} disinkronkan dari invoice {self.invoice_id.name}." + ) + + # Log di invoice utama + self.invoice_id.message_post( + body=f"Janji Bayar {self.promise_date} disinkronkan ke {len(selected_invoices)} invoice lain: {', '.join(selected_invoices.mapped('name'))}." + ) + return {'type': 'ir.actions.act_window_close'} + + +class SyncPromiseDateWizardLine(models.TransientModel): + _name = "sync.promise.date.wizard.line" + _description = "Sync Janji Bayar Wizard Line" + + wizard_id = fields.Many2one('sync.promise.date.wizard', string="Wizard") + invoice_id = fields.Many2one('account.move', string="Invoice") + sync_check = fields.Boolean(string="Sync?") + invoice_name = fields.Char(related="invoice_id.name", string="Nomor Invoice", readonly=True) + invoice_date_due = fields.Date(related="invoice_id.invoice_date_due", string="Due Date", readonly=True) + invoice_day_to_due = fields.Integer(related="invoice_id.invoice_day_to_due", string="Day to Due", readonly=True) + new_invoice_day_to_due = fields.Integer(related="invoice_id.new_invoice_day_to_due", string="New Day Due", readonly=True) + date_terima_tukar_faktur = fields.Date(related="invoice_id.date_terima_tukar_faktur", string="Tanggal Terima Tukar Faktur", readonly=True) + amount_total = fields.Monetary(related="invoice_id.amount_total", string="Total", readonly=True) + currency_id = fields.Many2one(related="invoice_id.currency_id", readonly=True)
\ No newline at end of file diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py index d354e3e3..40059bd9 100644 --- a/indoteknik_custom/models/account_move_due_extension.py +++ b/indoteknik_custom/models/account_move_due_extension.py @@ -14,6 +14,16 @@ class DueExtension(models.Model): number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True) partner_id = fields.Many2one('res.partner', string="Customer", readonly=True) order_id = fields.Many2one('sale.order', string="SO", readonly=True) + amount_total = fields.Monetary( + string="Amount Total SO", + compute="_compute_amount_total", + readonly=True + ) + currency_id = fields.Many2one( + 'res.currency', + related="order_id.currency_id", + readonly=True + ) invoice_id = fields.Many2one('account.move', related='due_line.invoice_id', string='Invoice', readonly=False) due_line = fields.One2many('due.extension.line', 'due_id', string='Due Extension Lines', auto_join=True) old_due = fields.Date(string="Old Due") @@ -34,6 +44,11 @@ class DueExtension(models.Model): approve_by = fields.Many2one('res.users', string="Approve By", readonly=True) date_approve = fields.Datetime(string="Date Approve", readonly=True) + @api.depends('order_id') + def _compute_amount_total(self): + for rec in self: + rec.amount_total = rec.order_id.amount_total if rec.order_id else 0.0 + def _compute_counter(self): for due in self: due.counter = due.partner_id.counter 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/product_template.py b/indoteknik_custom/models/product_template.py index f59bea6b..13e99707 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -1365,4 +1365,5 @@ class ImageCarousel(models.Model): _order = 'product_id, id' product_id = fields.Many2one('product.template', string='Product', required=True, ondelete='cascade', index=True, copy=False) + sequence = fields.Integer("Sequence", default=10) image = fields.Binary(string='Image') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 103a9131..50913a80 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -103,6 +103,11 @@ class PurchaseOrder(models.Model): string="BU Related Count", compute='_compute_bu_related_count' ) + + bills_related_count = fields.Integer( + string="Bills DP & Pelunasan", + compute="_compute_bills_related_count" + ) manufacturing_id = fields.Many2one('mrp.production', string='Manufacturing Orders') complete_bu_in_count = fields.Integer( @@ -260,6 +265,33 @@ class PurchaseOrder(models.Model): 'target': 'current', } + def action_view_bills(self): + self.ensure_one() + + bill_ids = [] + if self.bills_dp_id: + bill_ids.append(self.bills_dp_id.id) + if self.bills_pelunasan_id: + bill_ids.append(self.bills_pelunasan_id.id) + + return { + 'name': 'Bills (DP & Pelunasan)', + 'type': 'ir.actions.act_window', + 'res_model': 'account.move', + 'view_mode': 'tree,form', + 'target': 'current', + 'domain': [('id', 'in', bill_ids)], + } + + def _compute_bills_related_count(self): + for order in self: + count = 0 + if order.bills_dp_id: + count += 1 + if order.bills_pelunasan_id: + count += 1 + order.bills_related_count = count + # cek payment term def _check_payment_term(self): _logger.info("Check Payment Term Terpanggil") diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index e5e382a1..148a3fd0 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -29,7 +29,15 @@ class ResPartner(models.Model): reminder_invoices = fields.Boolean( string='Reminder Invoice?', - help='Centang jika kontak ini harus menerima email pengingat invoice.', default=False + help='Centang jika kontak ini harus menerima email pengingat invoice.', default=False, tracking=True + ) + dont_send_reminder_inv_parent = fields.Boolean( + string='Dont Send Reminder Invoices Parent?', + help='Centang jika kontak utama tidak perlu menerima sent Reminder Invoices Otomatis', default=False, tracking=True + ) + dont_send_reminder_inv_all = fields.Boolean( + string='Dont Send Reminder Invoices to All?', + help='Centang jika semua kontak utama dan child tidak menerima sent Reminder Invoices', default=False, tracking=True ) # informasi perusahaan @@ -275,7 +283,7 @@ class ResPartner(models.Model): ('email', '=', record.email) ], limit=1) - if existing_partner: + if existing_partner and not record.parent_id: raise ValidationError(f"Nama '{record.name}' dengan email '{record.email}' sudah digunakan oleh partner lain!") @api.constrains('npwp') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 0acfa0b0..ffb53dce 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -376,6 +376,83 @@ class SaleOrder(models.Model): compute='_compute_advance_payment_moves', store=False ) + reserved_percent = fields.Float( + string="Reserved %", digits=(16, 2), + compute="_compute_reserved_delivered_pie", store=False + ) + delivered_percent = fields.Float( + string="Delivered %", digits=(16, 2), + compute="_compute_reserved_delivered_pie", store=False + ) + unreserved_percent = fields.Float( + string="Unreserved %", digits=(16, 2), + compute="_compute_reserved_delivered_pie", store=False + ) + payment_state_custom = fields.Selection([ + ('unpaid', 'Unpaid'), + ('partial', 'Partially Paid'), + ('paid', 'Full Paid'), + ('no_invoice', 'No Invoice'), + ], 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): + for order in self: + invoices = order.invoice_ids.filtered(lambda inv: inv.state != 'cancel') + total = sum(invoices.mapped('amount_total')) + residual = sum(invoices.mapped('amount_residual')) + + if not invoices or total == 0: + order.payment_state_custom = 'no_invoice' + continue + + paid = total - residual + percent_paid = (paid / total) * 100 if total > 0 else 0.0 + + if percent_paid == 100: + order.payment_state_custom = 'paid' + elif percent_paid == 0: + order.payment_state_custom = 'unpaid' + 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', + 'order_line.move_ids.picking_type_id' + ) + def _compute_reserved_delivered_pie(self): + for order in self: + order_qty = sum(order.order_line.mapped('product_uom_qty')) or 0.0 + reserved_qty = delivered_qty = 0.0 + + 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 + + # --- CASE 2: Move sudah done --- + if move.location_dest_id.usage == 'customer': + # Barang dikirim ke customer + delivered_qty += move.quantity_done or 0.0 + elif move.location_id.usage == 'customer': + # Barang balik dari customer (retur) + delivered_qty -= move.quantity_done or 0.0 + + # Clamp supaya delivered gak minus + delivered_qty = max(delivered_qty, 0) + + # Hitung persen + 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 64b9f9bc..47a24264 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -54,7 +54,43 @@ class SaleOrderLine(models.Model): desc_updatable = fields.Boolean(string='desc boolean', default=True, compute='_get_desc_updatable') is_has_disc = fields.Boolean('Flash Sale', default=False) - + reserved_percent = fields.Float(string="Reserved %", digits=(16, 2), compute="_compute_reserved_delivered_pie", store=False) + delivered_percent = fields.Float(string="Delivered %", digits=(16, 2), compute="_compute_reserved_delivered_pie", store=False) + unreserved_percent = fields.Float(string="Unreserved %", digits=(16, 2), compute="_compute_reserved_delivered_pie", store=False) + + @api.depends( + 'move_ids.state', + 'move_ids.reserved_availability', + 'move_ids.quantity_done', + 'move_ids.picking_type_id' + ) + def _compute_reserved_delivered_pie(self): + for line in self: + order_qty = line.product_uom_qty or 0.0 + reserved_qty = delivered_qty = 0.0 + + if order_qty > 0: + for move in line.move_ids: + # --- CASE 1: Move belum selesai --- + if move.state not in ('done', 'cancel'): + reserved_qty += move.reserved_availability or 0.0 + continue + + # --- CASE 2: Move sudah done --- + if move.location_dest_id.usage == 'customer': + # Barang dikirim ke customer + delivered_qty += move.quantity_done or 0.0 + elif move.location_id.usage == 'customer': + # Barang balik dari customer (retur) + delivered_qty -= move.quantity_done or 0.0 + + # Clamp supaya delivered gak minus + delivered_qty = max(delivered_qty, 0) + + # Hitung persen + line.reserved_percent = min((reserved_qty / order_qty) * 100, 100) if order_qty else 0 + line.delivered_percent = min((delivered_qty / order_qty) * 100, 100) if order_qty else 0 + line.unreserved_percent = max(100 - line.reserved_percent - line.delivered_percent, 0) def _get_outgoing_incoming_moves(self): outgoing_moves = self.env['stock.move'] diff --git a/indoteknik_custom/models/solr/promotion_program_line.py b/indoteknik_custom/models/solr/promotion_program_line.py index 64ad4209..b1b2f88e 100644 --- a/indoteknik_custom/models/solr/promotion_program_line.py +++ b/indoteknik_custom/models/solr/promotion_program_line.py @@ -2,6 +2,10 @@ from odoo import models, api from typing import Type import pysolr import json +import logging +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) class PromotionProgramLine(models.Model): _inherit = 'promotion.program.line' @@ -20,58 +24,64 @@ class PromotionProgramLine(models.Model): def _sync_to_solr(self): solr_model = self.env['apache.solr'] - for rec in self: - document = solr_model.get_doc(self._solr_schema, rec.id) - - products = [{ - 'product_id': x.product_id.id, - 'qty': x.qty, - 'qty_sold': x.product_id.qty_sold - } for x in rec.product_ids] - - free_products = [{ - 'product_id': x.product_id.id, - 'qty': x.qty - } for x in rec.free_product_ids] - - promotion_type = rec._res_promotion_type() - - # Gathering all categories - category_names = [category.name for category in rec.product_ids.product_id.public_categ_ids] - - # Set sequence_i to None if rec.sequence is 0 - sequence_value = None if rec.sequence == 0 else rec.sequence - - document.update({ - 'id': rec.id, - 'program_id_i': rec.program_id.id or 0, - 'name_s': rec.name, - 'type_value_s': promotion_type['value'], - 'type_label_s': promotion_type['label'], - 'package_limit_i': rec.package_limit, - 'package_limit_user_i': rec.package_limit_user, - 'package_limit_trx_i': rec.package_limit_trx, - 'price_f': rec.price, - 'price_tier_1_f': rec.price_tier_1, - 'price_tier_2_f': rec.price_tier_2, - 'price_tier_3_f': rec.price_tier_3, - 'price_tier_4_f': rec.price_tier_4, - 'price_tier_5_f': rec.price_tier_5, - 'sequence_i': sequence_value, - 'product_ids': [x.product_id.id for x in rec.product_ids], - 'products_s': json.dumps(products), - 'free_product_ids': [x.product_id.id for x in rec.free_product_ids], - 'free_products_s': json.dumps(free_products), - 'total_qty_i': sum([x.qty for x in rec.product_ids] + [x.qty for x in rec.free_product_ids]), - 'total_qty_sold_f': [x.product_id.qty_sold for x in rec.product_ids], - 'active_b': rec.active, - "manufacture_name_s": rec.product_ids.product_id.x_manufacture.x_name or '', - "category_name": category_names, - }) - - self.solr().add([document]) - self.solr().commit() + try: + document = solr_model.get_doc(self._solr_schema, rec.id) + + products = [{ + 'product_id': x.product_id.id, + 'qty': x.qty, + 'qty_sold': x.product_id.qty_sold + } for x in rec.product_ids] + + free_products = [{ + 'product_id': x.product_id.id, + 'qty': x.qty + } for x in rec.free_product_ids] + + promotion_type = rec._res_promotion_type() + + category_names = [category.name for category in rec.product_ids.product_id.public_categ_ids] + sequence_value = None if rec.sequence == 0 else rec.sequence + + document.update({ + 'id': rec.id, + 'program_id_i': rec.program_id.id or 0, + 'name_s': rec.name, + 'type_value_s': promotion_type['value'], + 'type_label_s': promotion_type['label'], + 'package_limit_i': rec.package_limit, + 'package_limit_user_i': rec.package_limit_user, + 'package_limit_trx_i': rec.package_limit_trx, + 'price_f': rec.price, + 'price_tier_1_f': rec.price_tier_1, + 'price_tier_2_f': rec.price_tier_2, + 'price_tier_3_f': rec.price_tier_3, + 'price_tier_4_f': rec.price_tier_4, + 'price_tier_5_f': rec.price_tier_5, + 'sequence_i': sequence_value, + 'product_ids': [x.product_id.id for x in rec.product_ids], + 'products_s': json.dumps(products), + 'free_product_ids': [x.product_id.id for x in rec.free_product_ids], + 'free_products_s': json.dumps(free_products), + 'total_qty_i': sum([x.qty for x in rec.product_ids] + [x.qty for x in rec.free_product_ids]), + 'total_qty_sold_f': sum([x.product_id.qty_sold for x in rec.product_ids]), + 'active_b': rec.active, + "manufacture_name_s": rec.product_ids[0].product_id.x_manufacture.x_name or '', + "category_name": category_names, + }) + + self.solr().add([document]) + self.solr().commit() + + except Exception as e: + _logger.error( + "Failed to sync record %s (ID: %s) to Solr. Error: %s", + rec._name, rec.id, str(e), + exc_info=True # biar stack trace keluar + ) + # opsional -> kalau mau hard fail: + raise UserError(_("Sync to Solr failed for record %s: %s") % (rec.name, str(e))) @api.model def create(self, vals): diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d63c5d4c..a48e0ed1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -19,10 +19,10 @@ import re _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" -# biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" +biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" -biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" +# biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" class StockPicking(models.Model): @@ -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,6 +2072,8 @@ class CheckProduct(models.Model): _name = 'check.product' _description = 'Check Product' _order = 'picking_id, id' + _inherit = ['barcodes.barcode_events_mixin'] + picking_id = fields.Many2one( 'stock.picking', diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 4b03d4b0..699ee670 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)) @@ -420,8 +417,8 @@ class TukarGuling(models.Model): if not self.return_type: raise UserError("Return Type harus diisi!") - if operasi == 30 and self.operations.linked_manual_bu_out.state == 'done': - raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT sudah done") + # if operasi == 30 and self.operations.linked_manual_bu_out.state == 'done': + # raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT sudah done") if operasi == 30 and pp == 'tukar_guling': raise UserError("❌ BU/PICK tidak boleh di retur tukar guling") # else: diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index e20709e4..98ef5a8c 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -198,4 +198,6 @@ 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
\ No newline at end of file +access_purchase_order_update_date_wizard,access.purchase.order.update.date.wizard,model_purchase_order_update_date_wizard,base.group_user,1,1,1,1 +access_sync_promise_date_wizard,access.sync.promise.date.wizard,model_sync_promise_date_wizard,base.group_user,1,1,1,1 +access_sync_promise_date_wizard_line,access.sync.promise.date.wizard.line,model_sync_promise_date_wizard_line,base.group_user,1,1,1,1
\ No newline at end of file diff --git a/indoteknik_custom/static/src/js/check_product_barcode.js b/indoteknik_custom/static/src/js/check_product_barcode.js new file mode 100644 index 00000000..2fddc616 --- /dev/null +++ b/indoteknik_custom/static/src/js/check_product_barcode.js @@ -0,0 +1,76 @@ +odoo.define('indoteknik_custom.buffered_scanner', function (require) { + 'use strict'; + console.log('✅ Indoteknik_Custom JS Loaded'); + + var GAP_MS = 120; + var MIN_LEN = 3; + var COMMIT_TIMEOUT = 180; + + var buffer = ''; + var last = 0; + var timer = null; + + function reset() { + buffer = ''; + last = 0; + if (timer) { clearTimeout(timer); timer = null; } + } + + 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; + } + + // abaikan tombol kontrol (Shift, Tab, Arrow, Backspace, dll.) + if (key.length !== 1) return; + + var now = performance.now(); + var gap = now - (last || now); + last = now; + + if (!buffer || gap <= GAP_MS) { + // bagian dari "scan cepat" → tangani sendiri (hindari karakter hilang) + e.preventDefault(); + buffer += key; + + 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); + + document.addEventListener('focusin', function (e) { + if (isCodeProduct(e.target)) reset(); + }, true); +});
\ No newline at end of file diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml index e5d1cf8a..b399d4c9 100644 --- a/indoteknik_custom/views/account_move.xml +++ b/indoteknik_custom/views/account_move.xml @@ -68,6 +68,14 @@ <field name="nomor_kwitansi"/> <field name="down_payment"/> </field> + <field name="website_id" position="after"> + <field name="customer_promise_date"/> + <button name="action_sync_promise_date" + string="Sync Janji Bayar ke Invoice Lain" + type="object" + class="btn-primary" + help="Sync Janji Bayar Customer ke Invoices dengan jumlah Due Date yang sama"/> + </field> <field name="to_check" position="after"> <field name="already_paid"/> <field name="so_shipping_paid_by"/> @@ -195,5 +203,37 @@ <field name="state">code</field> <field name="code">action = records.export_faktur_to_xml()</field> </record> + + <record id="view_sync_promise_date_wizard_form" model="ir.ui.view"> + <field name="name">sync.promise.date.wizard.form</field> + <field name="model">sync.promise.date.wizard</field> + <field name="arch" type="xml"> + <form string="Sync Janji Bayar"> + <group> + <field name="invoice_id" readonly="1"/> + <field name="promise_date" readonly="1"/> + </group> + <field name="line_ids"> + <tree create="false" delete="false" editable="bottom"> + <field name="sync_check"/> + <field name="invoice_name" readonly="1"/> + <field name="invoice_date_due" readonly="1"/> + <field name="invoice_day_to_due" readonly="1"/> + <field name="new_invoice_day_to_due" readonly="1"/> + <field name="date_terima_tukar_faktur" readonly="1"/> + <field name="amount_total" readonly="1"/> + </tree> + </field> + + <button name="action_check_all" string="Check All" type="object" class="btn-secondary"/> + <button name="action_uncheck_all" string="Uncheck All" type="object" class="btn-secondary"/> + + <footer> + <button name="action_confirm" string="Konfirmasi Sync" type="object" class="btn-primary"/> + <button string="Batal" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> </data> </odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/account_move_views.xml b/indoteknik_custom/views/account_move_views.xml index 0fd7c9cd..7c1f8913 100644 --- a/indoteknik_custom/views/account_move_views.xml +++ b/indoteknik_custom/views/account_move_views.xml @@ -33,7 +33,7 @@ <field name="efaktur_id"/> <field name="reference"/> <field name="total_amt"/> - <field name="open_amt"/> + <field name="open_amt" sum="Grand Total Open Amount"/> </tree> </field> </record> @@ -68,6 +68,7 @@ <field name="partner_id" readonly="1"/> <field name="day_extension" attrs="{'readonly': [('is_approve', '=', True)]}"/> <field name="order_id" readonly="1"/> + <field name="amount_total" readonly="1"/> </group> <group> <field name="is_approve" readonly="1"/> 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/assets.xml b/indoteknik_custom/views/assets.xml new file mode 100644 index 00000000..4475004e --- /dev/null +++ b/indoteknik_custom/views/assets.xml @@ -0,0 +1,7 @@ +<odoo> + <template id="indoteknik_assets_backend" inherit_id="web.assets_backend" name="Indoteknik Custom Backend Assets"> + <xpath expr="." position="inside"> + <script type="text/javascript" src="/indoteknik_custom/static/src/js/check_product_barcode.js"/> + </xpath> + </template> +</odoo> diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index 514e6284..7c72d86a 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -164,6 +164,7 @@ <field name="priority" eval="15"/> <field name="arch" type="xml"> <search string="Search Customer Commision"> + <field name="number" string="Document Number"/> <field name="partner_ids"/> <group expand="0" string="Group By"> <filter string="Partner" name="group_partner" diff --git a/indoteknik_custom/views/dunning_run.xml b/indoteknik_custom/views/dunning_run.xml index 2117a7bb..210f7917 100644 --- a/indoteknik_custom/views/dunning_run.xml +++ b/indoteknik_custom/views/dunning_run.xml @@ -29,7 +29,7 @@ <field name="date_invoice"/> <field name="efaktur_id"/> <field name="reference"/> - <field name="total_amt"/> + <field name="total_amt" sum="Grand Total Amount"/> <field name="open_amt"/> <field name="due_date"/> </tree> diff --git a/indoteknik_custom/views/mail_template_invoice_reminder.xml b/indoteknik_custom/views/mail_template_invoice_reminder.xml index 8450be28..13c02a08 100644 --- a/indoteknik_custom/views/mail_template_invoice_reminder.xml +++ b/indoteknik_custom/views/mail_template_invoice_reminder.xml @@ -8,45 +8,50 @@ <field name="email_from">finance@indoteknik.co.id</field> <field name="email_to"></field> <field name="body_html" type="html"> - <div> + <div style="font-family:Arial, sans-serif; font-size:13px; color:#333;"> <p><b>Dear ${object.name},</b></p> <p>${days_to_due_message}</p> - <table border="1" cellpadding="4" cellspacing="0" style="border-collapse: collapse; font-size: 12px"> + <table cellpadding="6" cellspacing="0" width="100%" + style="border-collapse:collapse; font-size:12px; border:1px solid #ddd;"> <thead> - <tr style="background-color: #f2f2f2;" align="left"> - <th>Customer</th> - <th>No. PO</th> - <th>Invoice Number</th> - <th>Invoice Date</th> - <th>Due Date</th> - <th>Amount</th> - <th>Term</th> - <th>Days To Due</th> + <tr style="background-color:#f2f2f2; text-align:left;"> + <th style="border:1px solid #ddd;">Customer</th> + <th style="border:1px solid #ddd;">No. PO</th> + <th style="border:1px solid #ddd;">Invoice Number</th> + <th style="border:1px solid #ddd;">Invoice Date</th> + <th style="border:1px solid #ddd;">Due Date</th> + <th style="border:1px solid #ddd;">Amount</th> + <th style="border:1px solid #ddd;">Term</th> + <th style="border:1px solid #ddd;">Days To Due</th> </tr> </thead> <tbody> + <!-- baris invoice akan diinject dari Python --> </tbody> </table> <p>${closing_message}</p> + ${limit_info_html} <br/> <p>Terima Kasih.</p> - <br/> - <br/> - <p><b>Best Regards, - <br/> - <br/> - Widya R.<br/> - Dept. Finance<br/> - PT. INDOTEKNIK DOTCOM GEMILANG<br/> - <img src="https://erp.indoteknik.com/api/image/ir.attachment/datas/2135765" alt="Indoteknik" style="max-width: 18%; height: auto;"></img><br/> - <a href="https://wa.me/6285716970374" target="_blank">+62-857-1697-0374</a> | - <a href="mailto:finance@indoteknik.co.id">finance@indoteknik.co.id</a> - </b></p> - <p><i>Email ini dikirim secara otomatis. Abaikan jika pembayaran telah dilakukan.</i></p> - + <br/><br/> + <p> + <b> + Best Regards,<br/><br/> + Widya R.<br/> + Dept. Finance<br/> + PT. INDOTEKNIK DOTCOM GEMILANG<br/> + <img src="https://erp.indoteknik.com/api/image/ir.attachment/datas/2135765" + alt="Indoteknik" style="max-width:18%; height:auto;"/><br/> + <a href="https://wa.me/6285716970374" target="_blank">+62-857-1697-0374</a> | + <a href="mailto:finance@indoteknik.co.id">finance@indoteknik.co.id</a> + </b> + </p> + <p style="font-size:11px; color:#777;"> + <i>Email ini dikirim secara otomatis. Abaikan jika pembayaran telah dilakukan.</i> + </p> </div> </field> <field name="auto_delete" eval="True"/> diff --git a/indoteknik_custom/views/product_template.xml b/indoteknik_custom/views/product_template.xml index 8f9d1190..177449f4 100755 --- a/indoteknik_custom/views/product_template.xml +++ b/indoteknik_custom/views/product_template.xml @@ -66,6 +66,7 @@ <field name="model">image.carousel</field> <field name="arch" type="xml"> <tree editable="bottom"> + <field name="sequence" widget="handle"/> <field name="image" widget="image" width="80"/> </tree> </field> diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index 15cdc788..821f3295 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -14,6 +14,16 @@ attrs="{'invisible': ['|', ('sale_order_id', '=', False), ('state', 'not in', ['draft'])]}" /> </div> + <xpath expr="//button[@name='action_view_invoice']" position="after"> + <button type="object" + name="action_view_related_bu" + class="oe_stat_button" + icon="fa-truck" + attrs="{'invisible': [('state', 'in', ['draft', 'sent'])]}"> + <field name="bu_related_count" widget="statinfo" string="BU Related"/> + </button> + <field name="picking_count" invisible="1"/> + </xpath> <xpath expr="//button[@name='action_view_invoice']" position="before"> <field name="is_cab_visible" invisible="1"/> <button type="object" @@ -21,21 +31,19 @@ class="oe_stat_button" icon="fa-book" attrs="{'invisible': [('is_cab_visible', '=', False)]}" - style="width: 200px;"> + > <field name="move_id" widget="statinfo" string="Journal Uang Muka"/> <span class="o_stat_text"> <t t-esc="record.move_id.name"/> </span> </button> - <button type="object" - name="action_view_related_bu" - class="oe_stat_button" - icon="fa-truck" - style="width: 200px;" - attrs="{'invisible': [('state', 'in', ['draft', 'sent'])]}"> - <field name="bu_related_count" widget="statinfo" string="BU Related"/> + <button name="action_view_bills" + type="object" + icon="fa-pencil-square-o" + attrs="{'invisible': [ + ('bills_related_count', '=', 0)]}"> + <field string="Bills DP & Pelunasan" name="bills_related_count" widget="statinfo"/> </button> - <field name="picking_count" invisible="1"/> </xpath> <button id="draft_confirm" position="after"> <button name="po_approve" diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index b081f6f2..ca1a36de 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -87,6 +87,7 @@ <field name="use_so_approval" attrs="{'invisible': [('parent_id', '!=', False), ('company_type', '!=', 'company')]}" /> <field name="use_only_ready_stock" attrs="{'invisible': [('parent_id', '!=', False), ('company_type', '!=', 'company')]}" /> <field name="web_role" attrs="{'invisible': ['|', ('parent_id', '=', False), ('company_type', '=', 'company')]}" /> + <field name="reminder_invoices" attrs="{'invisible': ['|', ('parent_id', '=', False), ('company_type', '=', 'company')]}"/> </field> <xpath expr="//field[@name='child_ids']/form//field[@name='name']" position="before"> <field name="parent_id" invisible="1" /> @@ -215,6 +216,10 @@ <field name="dokumen_laporan_keuangan" /> <field name="dokumen_ktp_dirut" /> </group> + <group string="Reminder Invoices" attrs="{'invisible': [('parent_id', '!=', False), ('company_type', '!=', 'company')]}"> + <field name="dont_send_reminder_inv_parent"/> + <field name="dont_send_reminder_inv_all" /> + </group> </page> <page string="Aging Info"> <group string="Aging Info"> diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 868bce7b..a1a5e0cd 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -290,6 +290,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="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"/> @@ -477,6 +480,13 @@ <field name="date_kirim_ril"/> <field name="date_driver_departure"/> <field name="date_driver_arrival"/> + <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'" 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"/> diff --git a/indoteknik_custom/views/user_pengajuan_tempo.xml b/indoteknik_custom/views/user_pengajuan_tempo.xml index f9dca4ca..ad35d7fa 100644 --- a/indoteknik_custom/views/user_pengajuan_tempo.xml +++ b/indoteknik_custom/views/user_pengajuan_tempo.xml @@ -271,7 +271,7 @@ <field name="subject">Pengajuan Tempo Harus di Periksa!</field> <field name="email_from">"Indoteknik.com" <noreply@indoteknik.com></field> <field name="reply_to">sales@indoteknik.com</field> - <field name="email_to">finance@indoteknik.co.id, stephan@indoteknik.co.id, sapiabon768@gmail.com</field> + <field name="email_to">finance@indoteknik.co.id, stephan@indoteknik.co.id</field> <!-- <field name="email_to">sapiabon768@gmail.com</field>--> <field name="body_html" type="html"> <table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;"> |
