From b5a88d98b019c5a561864e56b8d73aaec4f4896a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 13 Aug 2025 14:06:54 +0700 Subject: (andri) try fix user company request via switchaccount --- indoteknik_custom/models/user_company_request.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py index 9216e8eb..2261545f 100644 --- a/indoteknik_custom/models/user_company_request.py +++ b/indoteknik_custom/models/user_company_request.py @@ -1,6 +1,9 @@ from odoo import models, fields, api from odoo.exceptions import UserError from odoo.http import request +import logging + +_logger = logging.getLogger(__name__) class UserCompanyRequest(models.Model): _name = 'user.company.request' @@ -65,8 +68,8 @@ class UserCompanyRequest(models.Model): is_approve = vals.get('is_approve') is_internal_input = vals.get('internal_input') is_company_id = vals.get('user_company_id') - if self.is_approve and is_approve: - raise UserError('Tidak dapat mengubah approval yang sudah diisi') + # if self.is_approve and is_approve: + # raise UserError('Tidak dapat mengubah approval yang sudah diisi') if is_internal_input: if self.user_company_id.nama_wajib_pajak == self.user_company_id.name: @@ -75,6 +78,7 @@ class UserCompanyRequest(models.Model): user_company_id = [] if is_company_id: user_company_id = request.env['res.partner'].search([('id', '=', is_company_id)], limit=1) + _logger.info('User Company ID: %s', user_company_id) # self.user_company_id.customer_type = self.similar_company_ids.customer_type # self.user_company_id.npwp = self.similar_company_ids.npwp # self.user_company_id.sppkp = self.similar_company_ids.sppkp @@ -101,6 +105,8 @@ class UserCompanyRequest(models.Model): self.user_id.property_account_payable_id = user_company_id.property_account_payable_id if user_company_id else self.user_company_id.property_account_payable_id self.user_id.property_payment_term_id = user_company_id.property_payment_term_id if user_company_id else self.user_company_id.property_payment_term_id self.user_id.property_supplier_payment_term_id = user_company_id.property_supplier_payment_term_id if user_company_id else self.user_company_id.property_supplier_payment_term_id + self.user_id.is_company = True + self.user_id.active = True self.user_company_id.active = True user.send_company_switch_approve_mail() if vals.get('is_switch_account') == True else user.send_company_request_approve_mail() else: -- cgit v1.2.3 From c920de946ec9e942ea98d5f46b8c20fe6c5d9f63 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 14 Aug 2025 13:53:19 +0700 Subject: (andri) fix isparent user --- indoteknik_custom/models/user_company_request.py | 45 ++++++++++++++---------- 1 file changed, 27 insertions(+), 18 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py index 2261545f..0c192239 100644 --- a/indoteknik_custom/models/user_company_request.py +++ b/indoteknik_custom/models/user_company_request.py @@ -75,7 +75,7 @@ class UserCompanyRequest(models.Model): if self.user_company_id.nama_wajib_pajak == self.user_company_id.name: self.user_company_id.nama_wajib_pajak = is_internal_input self.user_company_id.name = is_internal_input - user_company_id = [] + user_company_id = request.env['res.partner'] if is_company_id: user_company_id = request.env['res.partner'].search([('id', '=', is_company_id)], limit=1) _logger.info('User Company ID: %s', user_company_id) @@ -92,23 +92,32 @@ class UserCompanyRequest(models.Model): if not self.is_approve and is_approve: if is_approve == 'approved': - self.user_id.parent_id = user_company_id if user_company_id else self.user_company_id - self.user_id.customer_type = user_company_id.customer_type if user_company_id else self.user_company_id.customer_type - self.user_id.npwp = user_company_id.npwp if user_company_id else self.user_company_id.npwp - self.user_id.sppkp = user_company_id.sppkp if user_company_id else self.user_company_id.sppkp - self.user_id.nama_wajib_pajak = user_company_id.nama_wajib_pajak if user_company_id else self.user_company_id.nama_wajib_pajak - self.user_id.alamat_lengkap_text = user_company_id.alamat_lengkap_text if user_company_id else self.user_company_id.alamat_lengkap_text - self.user_id.industry_id = user_company_id.industry_id.id if user_company_id else self.user_company_id.industry_id - self.user_id.company_type_id = user_company_id.company_type_id.id if user_company_id else self.user_company_id.company_type_id - self.user_id.user_id = user_company_id.user_id if user_company_id else self.user_company_id.user_id - self.user_id.property_account_receivable_id = user_company_id.property_account_receivable_id if user_company_id else self.user_company_id.property_account_receivable_id - self.user_id.property_account_payable_id = user_company_id.property_account_payable_id if user_company_id else self.user_company_id.property_account_payable_id - self.user_id.property_payment_term_id = user_company_id.property_payment_term_id if user_company_id else self.user_company_id.property_payment_term_id - self.user_id.property_supplier_payment_term_id = user_company_id.property_supplier_payment_term_id if user_company_id else self.user_company_id.property_supplier_payment_term_id - self.user_id.is_company = True - self.user_id.active = True - self.user_company_id.active = True - user.send_company_switch_approve_mail() if vals.get('is_switch_account') == True else user.send_company_request_approve_mail() + company_rec = user_company_id if user_company_id else self.user_company_id + _logger.info('Company Record: %s', company_rec) + if not company_rec: + raise UserError("Company record tidak ditemukan untuk set parent_id.") + + self.user_id.parent_id = company_rec.id + self.user_id.customer_type = company_rec.customer_type + self.user_id.npwp = company_rec.npwp + self.user_id.sppkp = company_rec.sppkp + self.user_id.nama_wajib_pajak = company_rec.nama_wajib_pajak + self.user_id.alamat_lengkap_text = company_rec.alamat_lengkap_text + self.user_id.industry_id = company_rec.industry_id.id if company_rec.industry_id else False + self.user_id.company_type_id = company_rec.company_type_id.id if company_rec.company_type_id else False + self.user_id.user_id = company_rec.user_id + self.user_id.property_account_receivable_id = company_rec.property_account_receivable_id + self.user_id.property_account_payable_id = company_rec.property_account_payable_id + self.user_id.property_payment_term_id = company_rec.property_payment_term_id + self.user_id.property_supplier_payment_term_id = company_rec.property_supplier_payment_term_id + # self.user_id.is_company = True + self.user_id.active = True + company_rec.active = True + + if vals.get('is_switch_account') is True: + user.send_company_switch_approve_mail() + else: + user.send_company_request_approve_mail() else: # new_company = self.env['res.partner'].create({ # 'name': self.user_input -- cgit v1.2.3 From 33d3124e1283778cfb957c7bb75afc6619c4dd7d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 14 Aug 2025 15:11:00 +0700 Subject: (andri) fix --- indoteknik_custom/models/user_company_request.py | 45 ++++++++++-------------- 1 file changed, 18 insertions(+), 27 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py index 0c192239..2261545f 100644 --- a/indoteknik_custom/models/user_company_request.py +++ b/indoteknik_custom/models/user_company_request.py @@ -75,7 +75,7 @@ class UserCompanyRequest(models.Model): if self.user_company_id.nama_wajib_pajak == self.user_company_id.name: self.user_company_id.nama_wajib_pajak = is_internal_input self.user_company_id.name = is_internal_input - user_company_id = request.env['res.partner'] + user_company_id = [] if is_company_id: user_company_id = request.env['res.partner'].search([('id', '=', is_company_id)], limit=1) _logger.info('User Company ID: %s', user_company_id) @@ -92,32 +92,23 @@ class UserCompanyRequest(models.Model): if not self.is_approve and is_approve: if is_approve == 'approved': - company_rec = user_company_id if user_company_id else self.user_company_id - _logger.info('Company Record: %s', company_rec) - if not company_rec: - raise UserError("Company record tidak ditemukan untuk set parent_id.") - - self.user_id.parent_id = company_rec.id - self.user_id.customer_type = company_rec.customer_type - self.user_id.npwp = company_rec.npwp - self.user_id.sppkp = company_rec.sppkp - self.user_id.nama_wajib_pajak = company_rec.nama_wajib_pajak - self.user_id.alamat_lengkap_text = company_rec.alamat_lengkap_text - self.user_id.industry_id = company_rec.industry_id.id if company_rec.industry_id else False - self.user_id.company_type_id = company_rec.company_type_id.id if company_rec.company_type_id else False - self.user_id.user_id = company_rec.user_id - self.user_id.property_account_receivable_id = company_rec.property_account_receivable_id - self.user_id.property_account_payable_id = company_rec.property_account_payable_id - self.user_id.property_payment_term_id = company_rec.property_payment_term_id - self.user_id.property_supplier_payment_term_id = company_rec.property_supplier_payment_term_id - # self.user_id.is_company = True - self.user_id.active = True - company_rec.active = True - - if vals.get('is_switch_account') is True: - user.send_company_switch_approve_mail() - else: - user.send_company_request_approve_mail() + self.user_id.parent_id = user_company_id if user_company_id else self.user_company_id + self.user_id.customer_type = user_company_id.customer_type if user_company_id else self.user_company_id.customer_type + self.user_id.npwp = user_company_id.npwp if user_company_id else self.user_company_id.npwp + self.user_id.sppkp = user_company_id.sppkp if user_company_id else self.user_company_id.sppkp + self.user_id.nama_wajib_pajak = user_company_id.nama_wajib_pajak if user_company_id else self.user_company_id.nama_wajib_pajak + self.user_id.alamat_lengkap_text = user_company_id.alamat_lengkap_text if user_company_id else self.user_company_id.alamat_lengkap_text + self.user_id.industry_id = user_company_id.industry_id.id if user_company_id else self.user_company_id.industry_id + self.user_id.company_type_id = user_company_id.company_type_id.id if user_company_id else self.user_company_id.company_type_id + self.user_id.user_id = user_company_id.user_id if user_company_id else self.user_company_id.user_id + self.user_id.property_account_receivable_id = user_company_id.property_account_receivable_id if user_company_id else self.user_company_id.property_account_receivable_id + self.user_id.property_account_payable_id = user_company_id.property_account_payable_id if user_company_id else self.user_company_id.property_account_payable_id + self.user_id.property_payment_term_id = user_company_id.property_payment_term_id if user_company_id else self.user_company_id.property_payment_term_id + self.user_id.property_supplier_payment_term_id = user_company_id.property_supplier_payment_term_id if user_company_id else self.user_company_id.property_supplier_payment_term_id + self.user_id.is_company = True + self.user_id.active = True + self.user_company_id.active = True + user.send_company_switch_approve_mail() if vals.get('is_switch_account') == True else user.send_company_request_approve_mail() else: # new_company = self.env['res.partner'].create({ # 'name': self.user_input -- cgit v1.2.3 From aef7941fa025879bfa47ad626c0de7cb716e2ccc Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 15 Aug 2025 09:08:22 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index bf6834d0..d63c5d4c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1060,7 +1060,7 @@ class StockPicking(models.Model): self.sale_id.date_doc_kirim = self.date_doc_kirim def action_assign(self): - if self.env.context.get('default_picking_type_id') and self.sale_id: + if self.env.context.get('default_picking_type_id') and ('BU/INPUT' not in self.name or 'BU/PUT' not in self.name): pickings_to_assign = self.filtered( lambda p: not (p.sale_id and p.sale_id.hold_outgoing) ) -- cgit v1.2.3 From 8dd70f877c68abb3f0331e18f4652acb11e1bf84 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 15 Aug 2025 10:42:48 +0700 Subject: partial check product bom --- indoteknik_custom/models/mrp_production.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 91da0597..b39995b5 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -263,7 +263,7 @@ class CheckBomProduct(models.Model): "The product '%s' tidak ada di operations. " ) % record.product_id.display_name) - total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + total_qty_in_moves = sum(moves.mapped('quantity_done')) # Find existing lines for the same product, excluding the current line existing_lines = record.production_id.check_bom_product_lines.filtered( @@ -273,15 +273,15 @@ class CheckBomProduct(models.Model): if existing_lines: total_quantity = sum(existing_lines.mapped('quantity')) - if total_quantity > total_qty_in_moves: + if total_quantity != total_qty_in_moves: raise UserError(( - "Quantity Product '%s' kurang dari quantity demand." + "Quantity Product '%s' harus sama dengan quantity consumed." ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total - if record.quantity > total_qty_in_moves: + if record.quantity != total_qty_in_moves: raise UserError(( - "Quantity Product '%s' kurang dari quantity demand." + "Quantity Product '%s' harus sama dengan quantity consumed." ) % (record.product_id.display_name)) # Set the quantity to the entered value @@ -446,7 +446,7 @@ class CheckBomProduct(models.Model): "The product '%s' tidak ada di operations. " ) % record.product_id.display_name) - total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + total_qty_in_moves = sum(moves.mapped('quantity_done')) # Find existing lines for the same product, excluding the current line existing_lines = record.production_id.check_bom_product_lines.filtered( @@ -462,13 +462,13 @@ class CheckBomProduct(models.Model): if total_quantity > total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand." + "Quantity Product '%s' sudah melebihi quantity consumed." ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total if record.quantity == total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand." + "Quantity Product '%s' sudah melebihi quantity consumed." ) % (record.product_id.display_name)) # Set the quantity to the entered value -- cgit v1.2.3 From c9111bcf2c71a5dac6f13dc5aceb55956127a055 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 15 Aug 2025 13:59:36 +0700 Subject: (andri) fix --- indoteknik_custom/models/user_company_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py index 2261545f..284575aa 100644 --- a/indoteknik_custom/models/user_company_request.py +++ b/indoteknik_custom/models/user_company_request.py @@ -105,8 +105,8 @@ class UserCompanyRequest(models.Model): self.user_id.property_account_payable_id = user_company_id.property_account_payable_id if user_company_id else self.user_company_id.property_account_payable_id self.user_id.property_payment_term_id = user_company_id.property_payment_term_id if user_company_id else self.user_company_id.property_payment_term_id self.user_id.property_supplier_payment_term_id = user_company_id.property_supplier_payment_term_id if user_company_id else self.user_company_id.property_supplier_payment_term_id - self.user_id.is_company = True - self.user_id.active = True + # self.user_id.is_company = True + # self.user_id.active = True self.user_company_id.active = True user.send_company_switch_approve_mail() if vals.get('is_switch_account') == True else user.send_company_request_approve_mail() else: -- cgit v1.2.3 From e534cd541152921e9f041176ab18b86172367fa9 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 16 Aug 2025 13:01:54 +0700 Subject: Partner Map API --- indoteknik_custom/models/res_partner.py | 151 +++++++++++++++++++++++++------- 1 file changed, 120 insertions(+), 31 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index cf9fbea4..e5e382a1 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -199,16 +199,20 @@ class ResPartner(models.Model): alamat_lengkap_text = fields.Text(string="Alamat Lengkap", required=False, tracking=3) - def write(self, vals): - res = super(ResPartner, self).write(vals) + # def write(self, vals): + # res = super(ResPartner, self).write(vals) - for rec in self: - if 'latitude' in vals or 'longtitude' in vals: - rec._update_address_from_coords() + # use_pin = bool(self.env.context.get('use_pin')) - # Sinkronisasi payment_difficulty ke semua anak jika partner ini adalah parent - if not rec.parent_id and 'payment_difficulty' in vals: - rec.child_ids.write({'payment_difficulty': vals['payment_difficulty']}) + # for rec in self: + # if use_pin and 'latitude' in vals or 'longtitude' in vals: + # # rec._update_address_from_coords() + # rec.with_context(overwrite_street_from_pin=False)._update_address_from_coords() + + + # # Sinkronisasi payment_difficulty ke semua anak jika partner ini adalah parent + # if not rec.parent_id and 'payment_difficulty' in vals: + # rec.child_ids.write({'payment_difficulty': vals['payment_difficulty']}) # # # if 'property_payment_term_id' in vals: # # if not self.env.user.is_accounting and vals['property_payment_term_id'] != 26: @@ -219,17 +223,47 @@ class ResPartner(models.Model): # # if self.env.user.id not in users_in_group.mapped('id'): # # raise UserError('You name it') # + def write(self, vals): + res = super(ResPartner, self).write(vals) + + use_pin = bool(self.env.context.get('use_pin')) + for rec in self: + # Hanya jalan kalau explicit use_pin DAN ada lat/lng di payload + if use_pin and (('latitude' in vals) or ('longtitude' in vals)): + # optional guard: pastikan bukan kosong/0 + lat = rec.latitude + lng = rec.longtitude + if lat and lng and str(lat) not in ('0', '0.0') and str(lng) not in ('0', '0.0'): + rec.with_context(overwrite_street_from_pin=False)._update_address_from_coords() + + # Sinkronisasi payment_difficulty ke anak + if not rec.parent_id and 'payment_difficulty' in vals: + rec.child_ids.write({'payment_difficulty': vals['payment_difficulty']}) return res + + # @api.model + # def create(self, vals): + # records = super().create(vals) + # use_pin = bool(self.env.context.get('use_pin')) + # for rec in records: + # if use_pin and vals.get('latitude') and vals.get('longtitude'): + # rec._update_address_from_coords() + # if rec.parent_id and not vals.get('payment_difficulty'): + # rec.payment_difficulty = rec.parent_id.payment_difficulty + # return records + @api.model def create(self, vals): records = super().create(vals) + use_pin = bool(self.env.context.get('use_pin')) for rec in records: - if vals.get('latitude') and vals.get('longtitude'): - rec._update_address_from_coords() + if use_pin and vals.get('latitude') and vals.get('longtitude'): + rec.with_context(overwrite_street_from_pin=False)._update_address_from_coords() if rec.parent_id and not vals.get('payment_difficulty'): - rec.payment_difficulty = rec.parent_id.payment_difficulty + rec.payment_difficulty = rec.parent_id.payment_difficulty return records + @api.constrains('email') def _check_duplicate_name(self): @@ -621,6 +655,44 @@ class ResPartner(models.Model): else: raise UserError("Permintaan ke Google Maps gagal. Periksa koneksi internet atau API Key.") + # def _update_address_from_coords(self): + # for rec in self: + # if rec.latitude and rec.longtitude: + # try: + # components, formatted, parsed = rec._reverse_geocode(rec.latitude, rec.longtitude) + # if not parsed: + # continue + + # updates = { + # 'street': parsed.get('road') or '', + # 'zip': parsed.get('postcode') or '', + # 'address_map': formatted or '', + # } + + # if self.env.context.get('overwrite_street_from_pin', False): + # updates['street'] = parsed.get('road') or rec.street + + # state = self.env['res.country.state'].search([('name', 'ilike', parsed.get('state'))], limit=1) + # if state: + # updates['state_id'] = state.id + + # kota = self.env['vit.kota'].search([('name', 'ilike', parsed.get('city'))], limit=1) + # if kota: + # updates['kota_id'] = kota.id + + # kec = self.env['vit.kecamatan'].search([('name', 'ilike', parsed.get('district'))], limit=1) + # if kec: + # updates['kecamatan_id'] = kec.id + + # kel = self.env['vit.kelurahan'].search([('name', 'ilike', parsed.get('suburb'))], limit=1) + # if kel: + # updates['kelurahan_id'] = kel.id + + # rec.update(updates) + + # except Exception as e: + # raise UserError(f"Gagal update alamat dari koordinat: {str(e)}") + def _update_address_from_coords(self): for rec in self: if rec.latitude and rec.longtitude: @@ -630,28 +702,45 @@ class ResPartner(models.Model): continue updates = { - 'street': parsed.get('road') or '', - 'zip': parsed.get('postcode') or '', - 'address_map': formatted or '', + 'address_map': formatted or rec.address_map or '', } - state = self.env['res.country.state'].search([('name', 'ilike', parsed.get('state'))], limit=1) - if state: - updates['state_id'] = state.id - - kota = self.env['vit.kota'].search([('name', 'ilike', parsed.get('city'))], limit=1) - if kota: - updates['kota_id'] = kota.id - - kec = self.env['vit.kecamatan'].search([('name', 'ilike', parsed.get('district'))], limit=1) - if kec: - updates['kecamatan_id'] = kec.id - - kel = self.env['vit.kelurahan'].search([('name', 'ilike', parsed.get('suburb'))], limit=1) - if kel: - updates['kelurahan_id'] = kel.id - - rec.update(updates) + # ❌ Jangan isi street kecuali diminta eksplisit via context + if self.env.context.get('overwrite_street_from_pin', False): + road = parsed.get('road') + if road: + updates['street'] = road + + # (Opsional) admin area & zip hanya jika kosong agar tidak menimpa input user + if (not rec.zip) and parsed.get('postcode'): + updates['zip'] = parsed.get('postcode') + + state_name = parsed.get('state') + if state_name and not rec.state_id: + state = self.env['res.country.state'].search([('name', 'ilike', state_name)], limit=1) + if state: + updates['state_id'] = state.id + + city_name = parsed.get('city') + if city_name and not rec.kota_id: + kota = self.env['vit.kota'].search([('name', 'ilike', city_name)], limit=1) + if kota: + updates['kota_id'] = kota.id + + dist_name = parsed.get('district') + if dist_name and not rec.kecamatan_id: + kec = self.env['vit.kecamatan'].search([('name', 'ilike', dist_name)], limit=1) + if kec: + updates['kecamatan_id'] = kec.id + + sub_name = parsed.get('suburb') + if sub_name and not rec.kelurahan_id: + kel = self.env['vit.kelurahan'].search([('name', 'ilike', sub_name)], limit=1) + if kel: + updates['kelurahan_id'] = kel.id + + if updates: + rec.update(updates) except Exception as e: raise UserError(f"Gagal update alamat dari koordinat: {str(e)}") -- cgit v1.2.3 From f4f55acddc7d5b071289095b139614acce35308c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 19 Aug 2025 09:23:53 +0700 Subject: trying to fix bug solr --- indoteknik_custom/models/solr/product_template.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index c4aefe19..e362626b 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -121,12 +121,11 @@ class ProductTemplate(models.Model): "category_name": category_names, # Nama kategori sebagai list "description_t": template.website_description or '', "description_clean_t": cleaned_desc or '', - 'has_product_info_b': True, - 'publish_b': not template.unpublished, - 'sni_b': template.sni, - 'tkdn_b': template.tkdn, - "qty_sold_f": template.qty_sold, - "is_in_bu_b": is_in_bu, + 'has_product_info_b': self.bool_to_solr(True), + 'publish_b': self.bool_to_solr(not template.unpublished), + 'sni_b': self.bool_to_solr(template.sni), + 'tkdn_b': self.bool_to_solr(template.tkdn), + "is_in_bu_b": self.bool_to_solr(is_in_bu), "voucher_min_purchase_f" : voucher.min_purchase_amount or 0, "voucher_discount_type_s" : voucher.discount_type or '', "voucher_discount_amount_f" : voucher.discount_amount or 0, @@ -143,6 +142,10 @@ class ProductTemplate(models.Model): if not document.get('has_price_info_b'): template._sync_price_to_solr() + + def bool_to_solr(val): + return 'true' if val else 'false' + def get_category_hierarchy_ids(self, category_id): """ -- cgit v1.2.3 From 1e4cd196b7b41a38ee5f974667c421ae53c8e52f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 19 Aug 2025 09:30:43 +0700 Subject: add logger --- indoteknik_custom/models/solr/product_template.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index e362626b..b1c6654b 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -2,6 +2,9 @@ from datetime import datetime from bs4 import BeautifulSoup from odoo import api, fields, models +import logging + +_logger = logging.getLogger(__name__) class ProductTemplate(models.Model): @@ -132,6 +135,8 @@ class ProductTemplate(models.Model): "voucher_max_discount_f" : voucher.max_discount_amount or 0 }) + _logger.info(document) + self.solr().add(docs=[document], softCommit=True) products = self.env['product.product'].search([ ('product_tmpl_id', '=', template.id), -- cgit v1.2.3 From fd15be6548fa56ecf7df4fb44ab469eb9b4b4dc9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 19 Aug 2025 10:32:24 +0700 Subject: fix bug --- indoteknik_custom/models/solr/product_template.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index b1c6654b..c4aefe19 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -2,9 +2,6 @@ from datetime import datetime from bs4 import BeautifulSoup from odoo import api, fields, models -import logging - -_logger = logging.getLogger(__name__) class ProductTemplate(models.Model): @@ -124,19 +121,18 @@ class ProductTemplate(models.Model): "category_name": category_names, # Nama kategori sebagai list "description_t": template.website_description or '', "description_clean_t": cleaned_desc or '', - 'has_product_info_b': self.bool_to_solr(True), - 'publish_b': self.bool_to_solr(not template.unpublished), - 'sni_b': self.bool_to_solr(template.sni), - 'tkdn_b': self.bool_to_solr(template.tkdn), - "is_in_bu_b": self.bool_to_solr(is_in_bu), + 'has_product_info_b': True, + 'publish_b': not template.unpublished, + 'sni_b': template.sni, + 'tkdn_b': template.tkdn, + "qty_sold_f": template.qty_sold, + "is_in_bu_b": is_in_bu, "voucher_min_purchase_f" : voucher.min_purchase_amount or 0, "voucher_discount_type_s" : voucher.discount_type or '', "voucher_discount_amount_f" : voucher.discount_amount or 0, "voucher_max_discount_f" : voucher.max_discount_amount or 0 }) - _logger.info(document) - self.solr().add(docs=[document], softCommit=True) products = self.env['product.product'].search([ ('product_tmpl_id', '=', template.id), @@ -147,10 +143,6 @@ class ProductTemplate(models.Model): if not document.get('has_price_info_b'): template._sync_price_to_solr() - - def bool_to_solr(val): - return 'true' if val else 'false' - def get_category_hierarchy_ids(self, category_id): """ -- cgit v1.2.3 From 0d29ad7c92fbbf9c0f2a17502a7cc39e6c4df1ec Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 19 Aug 2025 10:59:04 +0700 Subject: fix --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d63c5d4c..cb36eb2f 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): -- cgit v1.2.3 From e2d186dd98b2c0d1bc9bab8ea9b7d80c59873046 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 19 Aug 2025 14:02:45 +0700 Subject: add percent pie on sale order --- indoteknik_custom/models/sale_order.py | 41 +++++++++++++++++++++++++++++ indoteknik_custom/models/sale_order_line.py | 27 +++++++++++++++++++ 2 files changed, 68 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 0acfa0b0..80790ebe 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -376,6 +376,47 @@ 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 + ) + + @api.depends('order_line.reserved_percent', 'order_line.delivered_percent', 'order_line.unreserved_percent') + def _compute_reserved_delivered_pie(self): + for order in self: + total_qty = sum(order.order_line.mapped('product_uom_qty')) + 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: + continue + + # ambil qty asli dari move, bukan percent agar akurat + pick_moves = line.move_ids.filtered( + lambda m: m.picking_type_id.code == 'internal' and m.state not in ('done', 'cancel') + ) + reserved_qty += sum(pick_moves.mapped('reserved_availability')) + + out_moves = line.move_ids.filtered( + lambda m: m.picking_type_id.code == 'outgoing' and m.state == 'done' + ) + delivered_qty += sum(out_moves.mapped('quantity_done')) + + 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 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..2a00bac0 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -54,6 +54,33 @@ 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') + 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: + # Reserved: hanya dari BU/PICK yang masih aktif + pick_moves = line.move_ids.filtered( + lambda m: m.picking_type_id.code == 'internal' and m.state not in ('done', 'cancel') + ) + reserved_qty = sum(pick_moves.mapped('reserved_availability')) + + # Delivered: hanya dari BU/OUT yang sudah done + out_moves = line.move_ids.filtered( + lambda m: m.picking_type_id.code == 'outgoing' and m.state == 'done' + ) + delivered_qty = sum(out_moves.mapped('quantity_done')) + + line.reserved_percent = (reserved_qty / order_qty) * 100 if order_qty else 0 + 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): -- cgit v1.2.3 From dde108ba7e3690b0ef655fe7449814e9021bab74 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 20 Aug 2025 09:58:00 +0700 Subject: (andri) add field janji bayar & button sync ke inv customer dengan due date yang sama --- indoteknik_custom/models/account_move.py | 48 +++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index b0ffd8b9..3a07cf87 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,46 @@ class AccountMove(models.Model): else: move.payment_date = False + def action_sync_promise_date(self): + for inv in self: + if not inv.customer_promise_date: + inv.env.user.notify_warning( + message="Isi Janji Bayar terlebih dahulu sebelum melakukan sinkronisasi.", + title="Sync Gagal", + ) + continue + + # Cari invoice lain milik partner yang due date sama + other_invoices = self.search([ + ('id', '!=', inv.id), + ('partner_id', '=', inv.partner_id.id), + ('invoice_date_due', '=', inv.invoice_date_due), + ('move_type', '=', 'out_invoice'), + ('state', '=', 'posted'), + ]) + + if not other_invoices: + inv.env.user.notify_info( + message="Tidak ada invoice lain dengan due date yang sama untuk disinkronkan.", + title="Sync Janji Bayar", + ) + continue + + # Sync field + other_invoices.write({'customer_promise_date': inv.customer_promise_date}) + + # Log di invoices lain + for other in other_invoices: + other.message_post( + body=f"Janji Bayar {inv.customer_promise_date} disinkronkan dari invoice {inv.name}." + ) + + # Log di invoice asal + other_names = ", ".join(other_invoices.mapped("name")) + inv.message_post( + body=f"Janji Bayar {inv.customer_promise_date} disinkronkan ke {len(other_invoices)} invoice lain: {other_names}." + ) + def send_due_invoice_reminder(self): today = fields.Date.today() @@ -279,7 +325,7 @@ class AccountMove(models.Model): } _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) + # template.send_mail(invs[0].id, force_send=True, email_values=values) # flag invs.write({'reminder_sent_date': today}) # Post ke chatter -- cgit v1.2.3 From 1d44c04f3ec25d3adc19eb8e34589c6461ef8a66 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 20 Aug 2025 10:04:10 +0700 Subject: fix bug percentpie retur --- indoteknik_custom/models/sale_order.py | 57 ++++++++++++++++++++++++----- indoteknik_custom/models/sale_order_line.py | 34 ++++++++++------- 2 files changed, 68 insertions(+), 23 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 80790ebe..53be999f 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -388,8 +388,37 @@ class SaleOrder(models.Model): string="Unreserved %", digits=(16, 2), compute="_compute_reserved_delivered_pie", store=False ) + payment_state_custom = fields.Selection([ + ('unpaid', 'Unpaid'), + ('partial', 'Partially Paid'), + ('paid', 'Paid'), + ('no_invoice', 'No Invoice'), + ], string="Payment Status", 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.reserved_percent', 'order_line.delivered_percent', 'order_line.unreserved_percent') + @api.depends('order_line.move_ids.state', + 'order_line.move_ids.reserved_availability', + 'order_line.move_ids.quantity_done') def _compute_reserved_delivered_pie(self): for order in self: total_qty = sum(order.order_line.mapped('product_uom_qty')) @@ -401,16 +430,23 @@ class SaleOrder(models.Model): if not order_qty: continue - # ambil qty asli dari move, bukan percent agar akurat - pick_moves = line.move_ids.filtered( - lambda m: m.picking_type_id.code == 'internal' and m.state not in ('done', 'cancel') - ) - reserved_qty += sum(pick_moves.mapped('reserved_availability')) + 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 - out_moves = line.move_ids.filtered( - lambda m: m.picking_type_id.code == 'outgoing' and m.state == 'done' - ) - delivered_qty += sum(out_moves.mapped('quantity_done')) + # 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) order.reserved_percent = (reserved_qty / total_qty) * 100 order.delivered_percent = (delivered_qty / total_qty) * 100 @@ -418,6 +454,7 @@ class SaleOrder(models.Model): else: order.reserved_percent = order.delivered_percent = order.unreserved_percent = 0 + def _has_ccm(self): if self.id: self.ccm_id = self.env['tukar.guling'].search([('origin', 'ilike', self.name)], limit=1) diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 2a00bac0..2406995d 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -58,31 +58,39 @@ class SaleOrderLine(models.Model): 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') + @api.depends('move_ids.state', 'move_ids.reserved_availability', 'move_ids.quantity_done') 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: - # Reserved: hanya dari BU/PICK yang masih aktif - pick_moves = line.move_ids.filtered( - lambda m: m.picking_type_id.code == 'internal' and m.state not in ('done', 'cancel') - ) - reserved_qty = sum(pick_moves.mapped('reserved_availability')) - - # Delivered: hanya dari BU/OUT yang sudah done - out_moves = line.move_ids.filtered( - lambda m: m.picking_type_id.code == 'outgoing' and m.state == 'done' - ) - delivered_qty = sum(out_moves.mapped('quantity_done')) - + for move in line.move_ids: + 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 + delivered_qty += move.quantity_done + elif move.location_id.usage == 'customer': + # Barang balik dari customer (retur) → kurangi delivered + delivered_qty -= move.quantity_done + + # Jangan sampai delivered minus + delivered_qty = max(delivered_qty, 0) + + # Hitung persentase line.reserved_percent = (reserved_qty / order_qty) * 100 if order_qty else 0 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'] -- cgit v1.2.3 From caaef86e4c60f026a2b2b7abcad355f2d18366c3 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 20 Aug 2025 10:10:03 +0700 Subject: (andri) add flag janji bayar di send mail reminder --- indoteknik_custom/models/account_move.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 3a07cf87..e5e68146 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -214,6 +214,15 @@ 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 + # Ambil child contact yang di-checklist reminder_invoices reminder_contacts = self.env['res.partner'].search([ ('parent_id', '=', partner.id), @@ -325,7 +334,7 @@ class AccountMove(models.Model): } _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) + template.send_mail(invs[0].id, force_send=True, email_values=values) # flag invs.write({'reminder_sent_date': today}) # Post ke chatter -- cgit v1.2.3 From ee81bf8bd22dc3c2431554b7302071b725fe3e90 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 20 Aug 2025 10:53:20 +0700 Subject: (andri) add button bills DP & Pelunasan --- indoteknik_custom/models/purchase_order.py | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'indoteknik_custom/models') 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") -- cgit v1.2.3 From 44b64eac732a3638f36686174350f82fcfb3b3c4 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Wed, 20 Aug 2025 11:09:21 +0700 Subject: Dragable Image Carousel Product Variant --- indoteknik_custom/models/product_template.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') 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') -- cgit v1.2.3 From 5691ea8569ab70a801ff5fc06adfe717a67224a3 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 20 Aug 2025 11:09:27 +0700 Subject: Block Manual Input from keyboard --- indoteknik_custom/models/stock_picking.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cb36eb2f..3d04a416 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -2070,6 +2070,8 @@ class CheckProduct(models.Model): _name = 'check.product' _description = 'Check Product' _order = 'picking_id, id' + _inherit = ['barcodes.barcode_events_mixin'] # ⬅️ aktifkan barcode handler + picking_id = fields.Many2one( 'stock.picking', @@ -2084,6 +2086,21 @@ 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: -- cgit v1.2.3 From fcef24bcce3d9e1d916bef3ff74429c990edf3b7 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 20 Aug 2025 14:00:06 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3d04a416..423cbb0a 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 -- cgit v1.2.3 From af483feb734c1e135a779008ca3fa32a721d59ff Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 20 Aug 2025 15:24:58 +0700 Subject: fix bug percent pie --- indoteknik_custom/models/sale_order.py | 35 +++++++++++++++++------------ indoteknik_custom/models/sale_order_line.py | 23 +++++++++++-------- 2 files changed, 35 insertions(+), 23 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 53be999f..5787c6ea 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -431,21 +431,27 @@ class SaleOrder(models.Model): continue for move in line.move_ids: - if move.state != 'done': - # reserve qty (draft/assigned) - if move.picking_type_id.code == 'internal': + if move.picking_type_id.code == 'internal': + if move.state != 'done': + # PICK belum done → qty reserved 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 + else: + # PICK done → qty_done masih dianggap reserved (masuk BU/OUT) + reserved_qty += move.quantity_done or 0.0 + + elif move.state == 'done': + # OUT done (customer terima) → delivered + if move.location_dest_id.usage == 'customer': + delivered_qty += move.quantity_done + # sekaligus kurangi reserved (karena udah terkirim) + reserved_qty -= move.quantity_done + # Return dari customer + elif move.location_id.usage == 'customer': + delivered_qty -= move.quantity_done + reserved_qty += move.quantity_done # balik ke reserved + + # clamp jangan sampai minus + reserved_qty = max(reserved_qty, 0) delivered_qty = max(delivered_qty, 0) order.reserved_percent = (reserved_qty / total_qty) * 100 @@ -455,6 +461,7 @@ class SaleOrder(models.Model): order.reserved_percent = order.delivered_percent = order.unreserved_percent = 0 + def _has_ccm(self): if self.id: self.ccm_id = self.env['tukar.guling'].search([('origin', 'ilike', self.name)], limit=1) 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'] -- cgit v1.2.3 From 1ca4ef399415974a0fac7a6c79780aff5181e0d6 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 20 Aug 2025 15:26:39 +0700 Subject: (andri) limit dan payment term aja yang masuk ke changelog --- indoteknik_custom/models/approval_payment_term.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') 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 -- cgit v1.2.3 From 3b4e4c77f0f0f83f3c739cd10c6e476c4dc39e7d Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 20 Aug 2025 15:26:43 +0700 Subject: Bug Invoice CCM --- indoteknik_custom/models/tukar_guling.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'indoteknik_custom/models') 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)) -- cgit v1.2.3 From c837117a771bbefcf06706d024d4620b331192a9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 20 Aug 2025 16:20:28 +0700 Subject: push --- indoteknik_custom/models/sale_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 5787c6ea..9a6f314d 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): -- cgit v1.2.3 From 919af07c186c43f2e9f41c005d2ba16aa5e8596d Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 21 Aug 2025 08:49:46 +0700 Subject: Unblock --- indoteknik_custom/models/stock_picking.py | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 423cbb0a..7e970c5d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -2072,8 +2072,6 @@ class CheckProduct(models.Model): _name = 'check.product' _description = 'Check Product' _order = 'picking_id, id' - _inherit = ['barcodes.barcode_events_mixin'] # ⬅️ aktifkan barcode handler - picking_id = fields.Many2one( 'stock.picking', @@ -2088,21 +2086,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: -- cgit v1.2.3 From 3b19e1ad701ddd8ab5d7d226a1776d857451b078 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 21 Aug 2025 09:10:57 +0700 Subject: (andri) fix --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index b0ffd8b9..bda9101d 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -278,8 +278,8 @@ 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']}") # flag invs.write({'reminder_sent_date': today}) # Post ke chatter -- cgit v1.2.3 From ebec9f8f1404d19e145b9d5c4218779faf9263e5 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 21 Aug 2025 11:07:57 +0700 Subject: (andri) add wizard sync janji bayar --- indoteknik_custom/models/account_move.py | 133 ++++++++++++++++++++++--------- 1 file changed, 95 insertions(+), 38 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index e5e68146..6713e87c 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -128,45 +128,35 @@ class AccountMove(models.Model): move.payment_date = False def action_sync_promise_date(self): - for inv in self: - if not inv.customer_promise_date: - inv.env.user.notify_warning( - message="Isi Janji Bayar terlebih dahulu sebelum melakukan sinkronisasi.", - title="Sync Gagal", - ) - continue - - # Cari invoice lain milik partner yang due date sama - other_invoices = self.search([ - ('id', '!=', inv.id), - ('partner_id', '=', inv.partner_id.id), - ('invoice_date_due', '=', inv.invoice_date_due), - ('move_type', '=', 'out_invoice'), - ('state', '=', 'posted'), - ]) - - if not other_invoices: - inv.env.user.notify_info( - message="Tidak ada invoice lain dengan due date yang sama untuk disinkronkan.", - title="Sync Janji Bayar", - ) - continue - - # Sync field - other_invoices.write({'customer_promise_date': inv.customer_promise_date}) - - # Log di invoices lain - for other in other_invoices: - other.message_post( - body=f"Janji Bayar {inv.customer_promise_date} disinkronkan dari invoice {inv.name}." - ) + self.ensure_one() + 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 - # Log di invoice asal - other_names = ", ".join(other_invoices.mapped("name")) - inv.message_post( - body=f"Janji Bayar {inv.customer_promise_date} disinkronkan ke {len(other_invoices)} invoice lain: {other_names}." - ) + 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() @@ -744,4 +734,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 -- cgit v1.2.3 From 80ed075e95d915f00c1abd0cde456516db439373 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 21 Aug 2025 13:06:43 +0700 Subject: (andri) fix --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index bda9101d..94774f0f 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -280,6 +280,7 @@ class AccountMove(models.Model): 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 +294,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') -- cgit v1.2.3 From 0e4e1eaf30f79afb2b9885c897b8a8a2044f8509 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 21 Aug 2025 16:17:48 +0700 Subject: add js --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3d04a416..534b1ff1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -2070,7 +2070,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( -- cgit v1.2.3 From 4b549234856b810bd99f8b1e18e01da90ccdc1e1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 21 Aug 2025 19:07:58 +0700 Subject: fix bug percent pie --- indoteknik_custom/models/sale_order.py | 66 +++++++++++++++------------------- 1 file changed, 29 insertions(+), 37 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 9a6f314d..ffb53dce 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -416,51 +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: - continue + if order_qty > 0: + # Kumpulin semua moves dari order + all_moves = order.order_line.mapped('move_ids') - for move in line.move_ids: - if move.picking_type_id.code == 'internal': - if move.state != 'done': - # PICK belum done → qty reserved - reserved_qty += move.reserved_availability or 0.0 - else: - # PICK done → qty_done masih dianggap reserved (masuk BU/OUT) - reserved_qty += move.quantity_done or 0.0 - - elif move.state == 'done': - # OUT done (customer terima) → delivered - if move.location_dest_id.usage == 'customer': - delivered_qty += move.quantity_done - # sekaligus kurangi reserved (karena udah terkirim) - reserved_qty -= move.quantity_done - # Return dari customer - elif move.location_id.usage == 'customer': - delivered_qty -= move.quantity_done - reserved_qty += move.quantity_done # balik ke reserved - - # clamp jangan sampai minus - reserved_qty = max(reserved_qty, 0) - delivered_qty = max(delivered_qty, 0) + 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 - 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 + # --- 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: -- cgit v1.2.3 From 3f28815704346d81e42ea03a95f591ef1131a622 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 21 Aug 2025 22:31:53 +0700 Subject: (andri) add flag dont send parent & all untuk reminder inv tempo --- indoteknik_custom/models/account_move.py | 17 +++++++++++------ indoteknik_custom/models/res_partner.py | 8 ++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index e09f9c5c..a211d739 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -213,6 +213,15 @@ class AccountMove(models.Model): ) 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_sent_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), @@ -220,12 +229,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: diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index e5e382a1..08b04e7d 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -31,6 +31,14 @@ class ResPartner(models.Model): string='Reminder Invoice?', help='Centang jika kontak ini harus menerima email pengingat invoice.', default=False ) + 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 + ) + 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 + ) # informasi perusahaan name_tempo = fields.Many2one('res.partner', string='Nama Perusahaan',tracking=True) -- cgit v1.2.3 From 882e368c75ab7663e1e6cf6a0dc78193f7dcd940 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 21 Aug 2025 22:43:59 +0700 Subject: (andri) add tracking di field dont send reminder inv --- indoteknik_custom/models/res_partner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 08b04e7d..1dba200a 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -29,15 +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 + 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 + help='Centang jika semua kontak utama dan child tidak menerima sent Reminder Invoices', default=False, tracking=True ) # informasi perusahaan -- cgit v1.2.3 From 70a2b42b40932936bb0cf194005c9523ebd8a9df Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 21 Aug 2025 23:05:24 +0700 Subject: (andri) fix typo --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index a211d739..8493bb24 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -219,7 +219,7 @@ class AccountMove(models.Model): _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_sent_reminder_inv_parent and partner.email: + if not partner.dont_send_reminder_inv_parent and partner.email: emails.append(partner.email) # Ambil child contact yang di-checklist reminder_invoices -- cgit v1.2.3 From dab315df817429db7a8445f4dd08c9722e4d328a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 22 Aug 2025 08:20:42 +0700 Subject: (andri) sync promise date hanya untuk User Finance --- indoteknik_custom/models/account_move.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 8493bb24..599b3220 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -129,6 +129,9 @@ class AccountMove(models.Model): 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.") @@ -177,7 +180,7 @@ class AccountMove(models.Model): ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), ('invoice_date_due', 'in', target_dates), ('date_terima_tukar_faktur', '!=', False) - ]) + ], limit=5) _logger.info(f"Invoices: {invoices}") invoices = invoices.filtered( @@ -328,7 +331,7 @@ class AccountMove(models.Model): 'reply_to': 'finance@indoteknik.co.id', } - template.send_mail(invs[0].id, force_send=True, email_values=values) + # 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 -- cgit v1.2.3 From 3b71cb2bb18f72e48c6f5013576a66ae982321da Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 22 Aug 2025 10:48:43 +0700 Subject: fix bug percent pie sale order line --- indoteknik_custom/models/sale_order_line.py | 44 +++++++++++++---------------- 1 file changed, 20 insertions(+), 24 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index a20f93ef..47a24264 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -58,7 +58,12 @@ class SaleOrderLine(models.Model): 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') + @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 @@ -66,35 +71,26 @@ class SaleOrderLine(models.Model): if order_qty > 0: for move in line.move_ids: - # --- CASE 1: Picking belum done --- - if move.state != 'done': - if move.picking_type_id.code == 'internal': - reserved_qty += move.reserved_availability or 0.0 + # --- CASE 1: Move belum selesai --- + if move.state not in ('done', 'cancel'): + reserved_qty += move.reserved_availability or 0.0 continue - # --- 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 - + # --- 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': - # Retur dari customer - delivered_qty -= move.quantity_done - reserved_qty += move.quantity_done + # Barang balik dari customer (retur) + delivered_qty -= move.quantity_done or 0.0 - # clamp supaya gak minus - reserved_qty = max(reserved_qty, 0) + # Clamp supaya delivered gak minus delivered_qty = max(delivered_qty, 0) - # Hitung persentase - line.reserved_percent = (reserved_qty / order_qty) * 100 if order_qty else 0 - line.delivered_percent = (delivered_qty / order_qty) * 100 if order_qty else 0 - line.unreserved_percent = 100 - line.reserved_percent - line.delivered_percent + # 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'] -- cgit v1.2.3 From a5fcc1fe80508de9c827d6f5c5d06db3262a8ab0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 25 Aug 2025 08:06:08 +0700 Subject: Fix bug ccm --- indoteknik_custom/models/tukar_guling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 23b9f085..699ee670 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -417,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: -- cgit v1.2.3 From e1621acf8c11052405aa3ca46ad160fedc310e0a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 25 Aug 2025 13:45:25 +0700 Subject: (andri) on reminder --- indoteknik_custom/models/account_move.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 599b3220..905855c6 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -180,7 +180,7 @@ class AccountMove(models.Model): ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), ('invoice_date_due', 'in', target_dates), ('date_terima_tukar_faktur', '!=', False) - ], limit=5) + ]) _logger.info(f"Invoices: {invoices}") invoices = invoices.filtered( @@ -331,7 +331,7 @@ class AccountMove(models.Model): 'reply_to': 'finance@indoteknik.co.id', } - # template.send_mail(invs[0].id, force_send=True, email_values=values) + 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 -- cgit v1.2.3 From 0fe7320b3b32fa41fd06e30040171c6bf9800897 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 26 Aug 2025 09:57:51 +0700 Subject: (andri) add grand total inv --- indoteknik_custom/models/account_move.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 599b3220..6f3190f0 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -247,8 +247,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""" {inv.partner_id.name} @@ -261,6 +263,15 @@ class AccountMove(models.Model): {days_to_due} """ + invoice_table_footer = f""" + + + Grand Total + {formatLang(self.env, grand_total, currency_obj=invs[0].currency_id)} + + + + """ days_to_due_message = "" closing_message = "" @@ -302,7 +313,7 @@ class AccountMove(models.Model): body_html = re.sub( r"]*>.*?", - f"{invoice_table_rows}", + f"{invoice_table_rows}{invoice_table_footer}", template.body_html, flags=re.DOTALL ).replace('${object.name}', partner.name) \ @@ -323,16 +334,16 @@ class AccountMove(models.Model): # Siapkan email values values = { 'subject': f"Reminder Invoice Due - {partner.name}", - # 'email_to': 'andrifebriyadiputra@gmail.com', - 'email_to': email_to, + 'email_to': 'andrifebriyadiputra@gmail.com', + # 'email_to': email_to, 'email_from': 'finance@indoteknik.co.id', - 'email_cc': ",".join(sorted(set(cc_list))), + # 'email_cc': ",".join(sorted(set(cc_list))), 'body_html': body_html, - 'reply_to': 'finance@indoteknik.co.id', + # 'reply_to': 'finance@indoteknik.co.id', } - # 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']}") + 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}) -- cgit v1.2.3 From bb75985009a318cbbe9c4410806e06ed07aae6d1 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 26 Aug 2025 13:58:48 +0700 Subject: (andri) add limit berdasarkan blocking amount dan limit terpakai berdasarkan total amount invoices yang ada --- indoteknik_custom/models/account_move.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 6f3190f0..49bfe762 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -273,6 +273,27 @@ class AccountMove(models.Model): """ + blocking_limit = partner.blocking_stage or 0.0 + + 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') + + ]) + _logger.info(f"Outstanding invoices for {partner.name}: {outstanding_invoices}") + outstanding_amount = sum(outstanding_invoices.mapped('amount_total')) + currency = invs[0].currency_id if invs else partner.company_id.currency_id + limit_info_html = f""" +

Informasi Tambahan:

+
    +
  • Total Limit: {formatLang(self.env, blocking_limit, currency_obj=currency)}
  • +
  • Total Limit Terpakai: {formatLang(self.env, outstanding_amount, currency_obj=currency)}
  • +
+ """ + days_to_due_message = "" closing_message = "" if dtd > 0: @@ -319,7 +340,7 @@ class AccountMove(models.Model): ).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 + limit_info_html) cc_list = [ 'finance@indoteknik.co.id', @@ -333,7 +354,7 @@ class AccountMove(models.Model): # Siapkan email values values = { - 'subject': f"Reminder Invoice Due - {partner.name}", + 'subject': f"Reminder Invoice Due Test - {partner.name}", 'email_to': 'andrifebriyadiputra@gmail.com', # 'email_to': email_to, 'email_from': 'finance@indoteknik.co.id', -- cgit v1.2.3 From 43049cea86883ffcfb6ae988dc46b74ad38def85 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 26 Aug 2025 17:05:58 +0700 Subject: (andri) fix layout mail reminder & add limit, sisa limit, limit terpakai, & jatuh tempo seperti pada tempo di webite --- indoteknik_custom/models/account_move.py | 66 +++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 10 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 49bfe762..f5dd5a39 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -179,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) - ], limit=5) + ('date_terima_tukar_faktur', '!=', False), + ('partner_id', 'in', [88813, 80163]) + ]) _logger.info(f"Invoices: {invoices}") invoices = invoices.filtered( @@ -275,23 +276,68 @@ class AccountMove(models.Model): 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') - ]) - _logger.info(f"Outstanding invoices for {partner.name}: {outstanding_invoices}") + 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 limit_info_html = f""" -

Informasi Tambahan:

-
    -
  • Total Limit: {formatLang(self.env, blocking_limit, currency_obj=currency)}
  • -
  • Total Limit Terpakai: {formatLang(self.env, outstanding_amount, currency_obj=currency)}
  • -
+ + + + + + + + + + + + + + + +
+ Pembayaran Tempo +
+

Kredit Limit Anda
+ {formatLang(self.env, blocking_limit, currency_obj=currency)} +

+

Status Detail Tempo Pembayaran Anda
+ {partner.property_payment_term_id.name or 'Review'} +

+
+
+ {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)} +
+
Sisa Kredit Limit
+
+
+ {formatLang(self.env, outstanding_amount, currency_obj=currency)} +
+
Kredit Limit Terpakai
+
{len(outstanding_invoices)} Transaksi
+
+
+ {formatLang(self.env, overdue_amount, currency_obj=currency)} +
+
Jatuh Tempo
+
{len(overdue_invoices)} Invoice
+
""" days_to_due_message = "" @@ -340,7 +386,7 @@ class AccountMove(models.Model): ).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 + limit_info_html) + .replace('${closing_message}', limit_info_html + closing_message) cc_list = [ 'finance@indoteknik.co.id', -- cgit v1.2.3 From cdeceb0e5325b00a1239470ab3493d84edd8328d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 27 Aug 2025 10:36:39 +0700 Subject: fix res partner --- indoteknik_custom/models/res_partner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 1dba200a..7a714ea7 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -279,11 +279,12 @@ class ResPartner(models.Model): if record.name: existing_partner = self.env['res.partner'].search([ ('id', '!=', record.id), + '|', ('name', '=', record.name), ('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') -- cgit v1.2.3 From 2103a438acc24ad44965b869a28a15424838c9b5 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 27 Aug 2025 13:39:59 +0700 Subject: (andri) 1. add sum open amount & add field amount total SO pada DE, 2. sum total amount pada dunning run --- indoteknik_custom/models/account_move_due_extension.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'indoteknik_custom/models') 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 -- cgit v1.2.3 From 491a66b7245379c23d0555c29d0d9d7861013144 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 27 Aug 2025 14:53:49 +0700 Subject: sum total qty sold --- indoteknik_custom/models/solr/promotion_program_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/promotion_program_line.py b/indoteknik_custom/models/solr/promotion_program_line.py index 64ad4209..4eafb9ac 100644 --- a/indoteknik_custom/models/solr/promotion_program_line.py +++ b/indoteknik_custom/models/solr/promotion_program_line.py @@ -64,7 +64,7 @@ class PromotionProgramLine(models.Model): '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], + '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.product_id.x_manufacture.x_name or '', "category_name": category_names, -- cgit v1.2.3 From 9add83a67fb62b20db56750f3d318debb84ea5e5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 27 Aug 2025 15:29:24 +0700 Subject: fix bug res partner duplicate email and name --- indoteknik_custom/models/res_partner.py | 1 - 1 file changed, 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 7a714ea7..148a3fd0 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -279,7 +279,6 @@ class ResPartner(models.Model): if record.name: existing_partner = self.env['res.partner'].search([ ('id', '!=', record.id), - '|', ('name', '=', record.name), ('email', '=', record.email) ], limit=1) -- cgit v1.2.3 From fbf4071010149637fed008c581424137577ee67f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 27 Aug 2025 15:36:54 +0700 Subject: fix bug program line --- indoteknik_custom/models/solr/promotion_program_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/promotion_program_line.py b/indoteknik_custom/models/solr/promotion_program_line.py index 4eafb9ac..64ad4209 100644 --- a/indoteknik_custom/models/solr/promotion_program_line.py +++ b/indoteknik_custom/models/solr/promotion_program_line.py @@ -64,7 +64,7 @@ class PromotionProgramLine(models.Model): '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]), + '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, -- cgit v1.2.3 From 1788dc7a4142f69e076891267d6e0567edd02d2f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 27 Aug 2025 15:47:28 +0700 Subject: add error log --- .../models/solr/promotion_program_line.py | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/promotion_program_line.py b/indoteknik_custom/models/solr/promotion_program_line.py index 64ad4209..a7459521 100644 --- a/indoteknik_custom/models/solr/promotion_program_line.py +++ b/indoteknik_custom/models/solr/promotion_program_line.py @@ -2,6 +2,9 @@ from odoo import models, api from typing import Type import pysolr import json +import logging + +_logger = logging.getLogger(__name__) class PromotionProgramLine(models.Model): _inherit = 'promotion.program.line' @@ -19,9 +22,9 @@ class PromotionProgramLine(models.Model): }) def _sync_to_solr(self): - solr_model = self.env['apache.solr'] - - for rec in self: + solr_model = self.env['apache.solr'] + for rec in self: + try: document = solr_model.get_doc(self._solr_schema, rec.id) products = [{ @@ -37,10 +40,7 @@ class PromotionProgramLine(models.Model): 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({ @@ -71,7 +71,16 @@ class PromotionProgramLine(models.Model): }) self.solr().add([document]) - self.solr().commit() + 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): -- cgit v1.2.3 From 97c13d37545bf30a39c4e2ba96928daa441acf51 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 27 Aug 2025 15:49:37 +0700 Subject: fix bug --- .../models/solr/promotion_program_line.py | 118 ++++++++++----------- 1 file changed, 59 insertions(+), 59 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/promotion_program_line.py b/indoteknik_custom/models/solr/promotion_program_line.py index a7459521..b4e82a36 100644 --- a/indoteknik_custom/models/solr/promotion_program_line.py +++ b/indoteknik_custom/models/solr/promotion_program_line.py @@ -22,65 +22,65 @@ class PromotionProgramLine(models.Model): }) def _sync_to_solr(self): - solr_model = self.env['apache.solr'] - for rec in self: - 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': [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() - - 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))) + solr_model = self.env['apache.solr'] + for rec in self: + 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': [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() + + 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): -- cgit v1.2.3 From 9b0ce82de8518012198f526737403648f94d21c7 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 27 Aug 2025 16:05:55 +0700 Subject: (andri) test --- indoteknik_custom/models/account_move.py | 70 +++++++++++--------------------- 1 file changed, 23 insertions(+), 47 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index d9d15764..b97edd0a 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -179,7 +179,8 @@ 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' , [14709]) ], limit=5) _logger.info(f"Invoices: {invoices}") @@ -295,48 +296,22 @@ class AccountMove(models.Model): currency = invs[0].currency_id if invs else partner.company_id.currency_id limit_info_html = f""" - - - - - - - - - - - - - - - -
- Pembayaran Tempo -
-

Kredit Limit Anda
- {formatLang(self.env, blocking_limit, currency_obj=currency)} -

-

Status Detail Tempo Pembayaran Anda
- {partner.property_payment_term_id.name or 'Review'} -

-
-
- {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)} -
-
Sisa Kredit Limit
-
-
- {formatLang(self.env, outstanding_amount, currency_obj=currency)} -
-
Kredit Limit Terpakai
-
{len(outstanding_invoices)} Transaksi
-
-
- {formatLang(self.env, overdue_amount, currency_obj=currency)} -
-
Jatuh Tempo
-
{len(overdue_invoices)} Invoice
-
+

Informasi Tambahan:

+
    +
  • Kredit Limit Anda: {formatLang(self.env, blocking_limit, currency_obj=currency)}
  • +
  • Status Detail Tempo: {partner.property_payment_term_id.name or 'Review'}
  • +
  • + Sisa Kredit Limit: {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)} +
  • +
  • + Kredit Limit Terpakai: {formatLang(self.env, outstanding_amount, currency_obj=currency)} + ({len(outstanding_invoices)} Transaksi) +
  • +
  • + Jatuh Tempo: {formatLang(self.env, overdue_amount, currency_obj=currency)} + ({len(overdue_invoices)} Invoice) +
  • +
""" days_to_due_message = "" @@ -385,7 +360,8 @@ class AccountMove(models.Model): ).replace('${object.name}', partner.name) \ .replace('${object.partner_id.name}', partner.name) \ .replace('${days_to_due_message}', days_to_due_message) \ - .replace('${closing_message}', limit_info_html + closing_message) + .replace('${closing_message}', closing_message) \ + .replace('${limit_info_html}', limit_info_html) cc_list = [ 'finance@indoteknik.co.id', @@ -408,11 +384,11 @@ class AccountMove(models.Model): # 'reply_to': 'finance@indoteknik.co.id', } - # 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']}") + 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}) + # invs.write({'reminder_sent_date': today}) # Post ke chatter user_system = self.env['res.users'].browse(25) system_id = user_system.partner_id.id if user_system else False -- cgit v1.2.3 From e8c10203b9cac4e8fe020a56f39945fbd360b605 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 27 Aug 2025 16:08:46 +0700 Subject: bug solr --- .../models/solr/promotion_program_line.py | 111 ++++++++++----------- 1 file changed, 51 insertions(+), 60 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/promotion_program_line.py b/indoteknik_custom/models/solr/promotion_program_line.py index b4e82a36..64ad4209 100644 --- a/indoteknik_custom/models/solr/promotion_program_line.py +++ b/indoteknik_custom/models/solr/promotion_program_line.py @@ -2,9 +2,6 @@ from odoo import models, api from typing import Type import pysolr import json -import logging - -_logger = logging.getLogger(__name__) class PromotionProgramLine(models.Model): _inherit = 'promotion.program.line' @@ -23,64 +20,58 @@ class PromotionProgramLine(models.Model): def _sync_to_solr(self): solr_model = self.env['apache.solr'] + for rec in self: - 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': [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() - - 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))) + 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() @api.model def create(self, vals): -- cgit v1.2.3 From 26623e1b2e8ba83367814ac704fbb19a4370f56c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 28 Aug 2025 11:29:54 +0700 Subject: fix bug --- .../models/solr/promotion_program_line.py | 112 +++++++++++---------- 1 file changed, 61 insertions(+), 51 deletions(-) (limited to 'indoteknik_custom/models') 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): -- cgit v1.2.3 From 4fcaaf9bc4e1e595e196437887dbe7c15acc5c41 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 28 Aug 2025 11:47:00 +0700 Subject: (andri) fix informasi tambahan dan menambahkan button cek selengkapnya --- indoteknik_custom/models/account_move.py | 45 ++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 17 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index b97edd0a..8e58ce6b 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -180,7 +180,7 @@ class AccountMove(models.Model): ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), ('invoice_date_due', 'in', target_dates), ('date_terima_tukar_faktur', '!=', False), - ('partner_id', 'in' , [14709]) + ('partner_id', 'in' , [94603]) ], limit=5) _logger.info(f"Invoices: {invoices}") @@ -295,23 +295,34 @@ class AccountMove(models.Model): overdue_amount = sum(overdue_invoices.mapped('amount_total')) currency = invs[0].currency_id if invs else partner.company_id.currency_id + # tempo_link = 'https://www.indoteknik.com/my/tempo' + tempo_link = 'http://localhost:2100/my/tempo' + limit_info_html = f""" -

Informasi Tambahan:

-
    -
  • Kredit Limit Anda: {formatLang(self.env, blocking_limit, currency_obj=currency)}
  • -
  • Status Detail Tempo: {partner.property_payment_term_id.name or 'Review'}
  • -
  • - Sisa Kredit Limit: {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)} -
  • -
  • - Kredit Limit Terpakai: {formatLang(self.env, outstanding_amount, currency_obj=currency)} - ({len(outstanding_invoices)} Transaksi) -
  • -
  • - Jatuh Tempo: {formatLang(self.env, overdue_amount, currency_obj=currency)} - ({len(overdue_invoices)} Invoice) -
  • -
+

Informasi Tambahan:

+
    +
  • Kredit Limit Anda: {formatLang(self.env, blocking_limit, currency_obj=currency)}
  • +
  • Status Detail Tempo: {partner.property_payment_term_id.name or 'Review'}
  • +
  • + Sisa Kredit Limit: {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)} +
  • +
  • + Kredit Limit Terpakai: {formatLang(self.env, outstanding_amount, currency_obj=currency)} + ({len(outstanding_invoices)} Transaksi) +
  • +
  • + Jatuh Tempo: {formatLang(self.env, overdue_amount, currency_obj=currency)} + ({len(overdue_invoices)} Invoice) +
  • +
+

+ + Cek Selengkapnya + +

""" days_to_due_message = "" -- cgit v1.2.3 From 6f6c6c86d91c811984d6ce6c478f4024cd4fb3ed Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 28 Aug 2025 16:35:58 +0700 Subject: (andri) live --- indoteknik_custom/models/account_move.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 8e58ce6b..c44cad78 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -295,8 +295,8 @@ class AccountMove(models.Model): overdue_amount = sum(overdue_invoices.mapped('amount_total')) currency = invs[0].currency_id if invs else partner.company_id.currency_id - # tempo_link = 'https://www.indoteknik.com/my/tempo' - tempo_link = 'http://localhost:2100/my/tempo' + tempo_link = 'https://indoteknik.com/my/tempo' + # tempo_link = 'http://localhost:2100/my/tempo' limit_info_html = f"""

Informasi Tambahan:

@@ -386,20 +386,20 @@ class AccountMove(models.Model): # Siapkan email values values = { - 'subject': f"Reminder Invoice Due Test - {partner.name}", - 'email_to': 'andrifebriyadiputra@gmail.com', - # 'email_to': email_to, + 'subject': f"Reminder Invoice Due - {partner.name}", + # 'email_to': 'andrifebriyadiputra@gmail.com', + 'email_to': email_to, 'email_from': 'finance@indoteknik.co.id', - # 'email_cc': ",".join(sorted(set(cc_list))), + 'email_cc': ",".join(sorted(set(cc_list))), 'body_html': body_html, - # 'reply_to': 'finance@indoteknik.co.id', + 'reply_to': 'finance@indoteknik.co.id', } 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"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}) + invs.write({'reminder_sent_date': today}) # Post ke chatter user_system = self.env['res.users'].browse(25) system_id = user_system.partner_id.id if user_system else False -- cgit v1.2.3 From fd2d67cacb3015e346b9656b7329606066c2f95a Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 29 Aug 2025 15:01:45 +0700 Subject: fix return selain SO PO --- indoteknik_custom/models/stock_picking_return.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index 1fc8d088..88acf83c 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -110,7 +110,7 @@ class ReturnPicking(models.TransientModel): if mapping_koli_vals: context['default_mapping_koli_ids'] = mapping_koli_vals - if picking.purchase_id or 'PO' in picking.origin: + if picking.purchase_id or 'PO' in (picking.origin or ''): _logger.info("Redirect ke Tukar Guling PO via purchase_id / origin") return { 'name': _('Tukar Guling PO'), @@ -120,7 +120,7 @@ class ReturnPicking(models.TransientModel): 'target': 'current', 'context': context, } - else: + if picking.sale_id or 'SO' in (picking.origin or ''): _logger.info("This picking is NOT from a PO, fallback to SO.") return { 'name': _('Tukar Guling SO'), @@ -130,6 +130,9 @@ class ReturnPicking(models.TransientModel): 'target': 'current', 'context': context, } + else: + _logger.info("Bukan SO/PO → retur standar (create_returns)") + return super(ReturnPicking, self).create_returns() class ReturnPickingLine(models.TransientModel): -- cgit v1.2.3