From ae360ebbe575c5ede395b0b396b9627b89b0e226 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 2 Oct 2025 13:53:42 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 16e235da..ae7121da 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -2552,9 +2552,22 @@ class ScanKoli(models.Model): out_moves = self.env['stock.move.line'].search([('picking_id', '=', picking.linked_out_picking_id.id)]) for pick_move in pick_moves: - corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) - if corresponding_out_move: - corresponding_out_move.qty_done += pick_move.qty_done + corresponding_out_moves = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) + + if len(corresponding_out_moves) == 1: + corresponding_out_moves.qty_done += pick_move.qty_done + + elif len(corresponding_out_moves) > 1: + qty_koli = pick_move.qty_done + for out_move in corresponding_out_moves: + if qty_koli <= 0: + break + # ambil sesuai kebutuhan atau sisa qty + qty_to_assign = min(qty_koli, out_move.product_uom_qty) + out_move.qty_done += qty_to_assign + qty_koli -= qty_to_assign + + def _reset_qty_done_if_no_scan(self, picking_id): product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) -- cgit v1.2.3 From 6baf4d55e6dbaef2f5bd270a18effa55a821677a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 2 Oct 2025 14:45:43 +0700 Subject: push --- indoteknik_custom/models/mrp_production.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index b39995b5..4ebfa244 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -21,6 +21,21 @@ class MrpProduction(models.Model): ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False) + def action_cancel(self): + for production in self: + moves_with_forecast = production.move_raw_ids.filtered( + lambda m: m.forecast_availability > 0 + ) + if moves_with_forecast: + # bikin list produk per baris + product_list = "\n".join( + "- %s" % p.display_name for p in moves_with_forecast.mapped('product_id') + ) + raise UserError(_( + "You cannot cancel this Manufacturing Order because the following raw materials " + "still have forecast availability:\n\n%s" % product_list + )) + return super(MrpProduction, self).action_cancel() @api.constrains('check_bom_product_lines') def constrains_check_bom_product_lines(self): -- cgit v1.2.3 From 67294c9cacdf2e4da1348c41cb23a02d50aea374 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 2 Oct 2025 15:38:34 +0700 Subject: fixb ug --- indoteknik_custom/models/mrp_production.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 4ebfa244..30956082 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -24,8 +24,9 @@ class MrpProduction(models.Model): def action_cancel(self): for production in self: moves_with_forecast = production.move_raw_ids.filtered( - lambda m: m.forecast_availability > 0 + lambda m: m.reserved_availability > 0 ) + if moves_with_forecast: # bikin list produk per baris product_list = "\n".join( -- cgit v1.2.3 From 35d01765f9b925467b6ac20d2ff83c81f49e4d3e Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 2 Oct 2025 16:03:44 +0700 Subject: fix cannot apply voucher when cart has promotion item --- indoteknik_custom/models/sale_order.py | 65 +++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 663cba58..3aaae12d 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2871,6 +2871,7 @@ class SaleOrder(models.Model): def action_apply_voucher(self): for line in self.order_line: if line.order_promotion_id: + _logger.warning(f"[CHECKOUT FAILED] Produk promo ditemukan: {line.product_id.display_name}") raise UserError('Voucher tidak dapat digabung dengan promotion program') voucher = self.voucher_id @@ -2913,42 +2914,82 @@ class SaleOrder(models.Model): self.apply_voucher_shipping() def apply_voucher(self): + def _is_promo_line(line): + # TRUE jika baris tidak boleh kena voucher + if getattr(line, 'order_promotion_id', False): + return True # baris dari program promo + if (line.price_unit or 0.0) == 0.0: + return True # free item + if getattr(line, 'is_has_disc', False): + return True # sudah promo/flashsale/berdiskon + if (line.discount or 0.0) >= 100.0: + return True # safety + return False + + # --- LOOP 1: susun input untuk voucher.apply() --- order_line = [] for line in self.order_line: + if _is_promo_line(line): + continue order_line.append({ 'product_id': line.product_id, 'price': line.price_unit, 'discount': line.discount, 'qty': line.product_uom_qty, - 'subtotal': line.price_subtotal + 'subtotal': line.price_subtotal, }) + + if not order_line: + return + voucher = self.voucher_id.apply(order_line) + # --- LOOP 2: tulis hasilnya HANYA ke non-promo --- for line in self.order_line: + if _is_promo_line(line): + continue + line.initial_discount = line.discount voucher_type = voucher['type'] - used_total = voucher['total'][voucher_type] - used_discount = voucher['discount'][voucher_type] + total_map = voucher['total'][voucher_type] + discount_map = voucher['discount'][voucher_type] - manufacture_id = line.product_id.x_manufacture.id if voucher_type == 'brand': - used_total = used_total.get(manufacture_id) - used_discount = used_discount.get(manufacture_id) + m_id = line.product_id.x_manufacture.id + used_total = (total_map or {}).get(m_id) + used_discount = (discount_map or {}).get(m_id) + else: + used_total = total_map + used_discount = discount_map - if not used_total or not used_discount: + if not used_total or not used_discount or (line.product_uom_qty or 0.0) == 0.0: continue line_contribution = line.price_subtotal / used_total line_voucher = used_discount * line_contribution - line_voucher_item = line_voucher / line.product_uom_qty + per_item_voucher = line_voucher / line.product_uom_qty - line_price_unit = line.price_unit / 1.11 if any(tax.id == 23 for tax in line.tax_id) else line.price_unit - line_discount_item = line_price_unit * line.discount / 100 + line_voucher_item - line_voucher_item = line_discount_item / line_price_unit * 100 + has_ppn_11 = any(tax.id == 23 for tax in line.tax_id) + base_unit = line.price_unit / 1.11 if has_ppn_11 else line.price_unit + + new_disc_value = base_unit * line.discount / 100 + per_item_voucher + new_disc_pct = (new_disc_value / base_unit) * 100 line.amount_voucher_disc = line_voucher - line.discount = line_voucher_item + line.discount = new_disc_pct + + _logger.info( + "[VOUCHER_APPLIED] SO=%s voucher=%s type=%s line_id=%s product=%s qty=%s discount_pct=%.2f amount_voucher=%s", + self.name, + getattr(self.voucher_id, "code", None), + voucher.get("type"), + line.id, + line.product_id.display_name, + line.product_uom_qty, + line.discount, + line.amount_voucher_disc, + ) self.amount_voucher_disc = voucher['discount']['all'] self.applied_voucher_id = self.voucher_id -- cgit v1.2.3 From 6dda561fa704add3be34ecb16fa44e72fcf1bf56 Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Fri, 3 Oct 2025 09:44:09 +0700 Subject: push --- indoteknik_custom/models/refund_sale_order.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index de9870f6..a866af6e 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -571,7 +571,9 @@ class RefundSaleOrder(models.Model): domain = [ ('journal_id', '=', 11), ('state', '=', 'posted'), - ('ref', 'ilike', 'dp') + '|', + ('ref', 'ilike', 'dp'), + ('ref', 'ilike', 'payment'), ] domain += ['|'] * (len(so_names) - 1) for n in so_names: -- cgit v1.2.3 From baaaa901391b4cd6956cb28aff992735e84962e4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 3 Oct 2025 19:38:57 +0700 Subject: CR BU/IU Approval flow --- indoteknik_custom/models/stock_picking.py | 46 +++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ae7121da..eea87926 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -176,6 +176,28 @@ class StockPicking(models.Model): linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) area_name = fields.Char(string="Area", compute="_compute_area_name") + is_bu_iu = fields.Boolean('Is BU/IU', compute='_compute_is_bu_iu', default=False, copy=False, readonl=True) + + @api.depends('name') + def _compute_is_bu_iu(self): + for record in self: + if 'BU/IU' in record.name: + record.is_bu_iu = True + else: + record.is_bu_iu = False + + def action_bu_iu_to_pengajuan2(self): + for rec in self: + if not rec.is_bu_iu or not rec.is_internal_use: + raise UserError(_("Tombol ini hanya untuk dokumen BU/IU - Internal Use.")) + if rec.approval_status == False: + raise UserError("Harus Ask Approval terlebih dahulu") + if rec.approval_status in ['pengajuan1'] and self.env.user.is_accounting: + rec.approval_status = 'pengajuan2' + # raise UserError(_("Hanya bisa dijalankan saat status di 'pengajuan1'.")) + rec.message_post(body=_("Status naik ke Approval Logistik oleh %s") % self.env.user.display_name) + + return True # def _get_biteship_api_key(self): # # return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') @@ -1082,9 +1104,12 @@ class StockPicking(models.Model): def ask_approval(self): - if self.env.user.is_accounting: - raise UserError("Bisa langsung Validate") - if self.env.user.is_logistic_approver and self.location_id.id == 57 or self.location_id== 57: + # if self.env.user.is_accounting: + # if self.env.user.is_accounting and self.location_id.id == 57 or self.location_id == 57 and self.approval_status in ['pengajuan1', ''] and 'BU/IU' in self.name and self.approval_status == 'pengajuan1': + # raise UserError("Bisa langsung set ke approval logistik") + if self.env.user.is_accounting and self.approval_status == "pengajuan2" and 'BU/IU' in self.name: + raise UserError("Tidak perlu ask approval sudah approval logistik") + if self.env.user.is_logistic_approver and self.location_id.id == 57 or self.location_id== 57 and self.approval_status == 'pengajuan2' and 'BU/IU' in self.name: raise UserError("Bisa langsung Validate") @@ -1110,9 +1135,7 @@ class StockPicking(models.Model): if line.qty_done <= 0: raise UserError("Qty tidak boleh 0") pick.approval_status = 'pengajuan1' - if pick.location_id.id == 57: - pick.approval_status = 'pengajuan2' - return + def ask_receipt_approval(self): if self.env.user.is_logistic_approver: @@ -1315,10 +1338,10 @@ class StockPicking(models.Model): if self.picking_type_id.code == 'incoming' and self.group_id.id == False and self.is_internal_use == False: raise UserError(_('Tidak bisa Validate jika tidak dari Document SO / PO')) - if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57: + if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57 and self.approval_status == 'pengajuan2': raise UserError("Harus di Approve oleh Logistik") - if self.is_internal_use and not self.env.user.is_accounting: + if self.is_internal_use and not self.env.user.is_accounting and self.approval_status == 'pengajuan1' and self.location_id.id == 57: raise UserError("Harus di Approve oleh Accounting") if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: @@ -1327,8 +1350,10 @@ class StockPicking(models.Model): if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager: raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara") - if self.is_internal_use: + if self.is_internal_use and self.approval_status == 'pengajuan2': self.approval_status = 'approved' + elif self.is_internal_use and self.approval_status in ['pengajuan1', '', False]: + raise UserError("Tidak Bisa Validate, set approval status ke approval logistik terlebih dahhulu") elif self.picking_type_id.code == 'incoming': self.approval_receipt_status = 'approved' @@ -1360,9 +1385,6 @@ class StockPicking(models.Model): ) self.validation_minus_onhand_quantity() - loc = self.location_id - if loc.id == 57 and not self.env.user.is_logistic_approver and self.approval_status in ['pengajuan2']: - raise UserError ("Harus Ask Approval Logistik") self.responsible = self.env.user.id # self.send_koli_to_so() if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name: -- cgit v1.2.3 From 63bb46e84f44bdcb286bbca544f65b03a7d98636 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 6 Oct 2025 09:05:59 +0700 Subject: rollback --- indoteknik_custom/models/stock_picking.py | 67 +++++++------------------------ 1 file changed, 14 insertions(+), 53 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index eea87926..43e45c09 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -176,28 +176,6 @@ class StockPicking(models.Model): linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) area_name = fields.Char(string="Area", compute="_compute_area_name") - is_bu_iu = fields.Boolean('Is BU/IU', compute='_compute_is_bu_iu', default=False, copy=False, readonl=True) - - @api.depends('name') - def _compute_is_bu_iu(self): - for record in self: - if 'BU/IU' in record.name: - record.is_bu_iu = True - else: - record.is_bu_iu = False - - def action_bu_iu_to_pengajuan2(self): - for rec in self: - if not rec.is_bu_iu or not rec.is_internal_use: - raise UserError(_("Tombol ini hanya untuk dokumen BU/IU - Internal Use.")) - if rec.approval_status == False: - raise UserError("Harus Ask Approval terlebih dahulu") - if rec.approval_status in ['pengajuan1'] and self.env.user.is_accounting: - rec.approval_status = 'pengajuan2' - # raise UserError(_("Hanya bisa dijalankan saat status di 'pengajuan1'.")) - rec.message_post(body=_("Status naik ke Approval Logistik oleh %s") % self.env.user.display_name) - - return True # def _get_biteship_api_key(self): # # return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') @@ -213,7 +191,7 @@ class StockPicking(models.Model): # def write(self, vals): # if 'linked_manual_bu_out' in vals: # for record in self: - # if (record.picking_type_code == 'internal' + # if (record.picking_type_code == 'internal' # and 'BU/PICK/' in record.name): # # Jika menghapus referensi (nilai di-set False/None) # if record.linked_manual_bu_out and not vals['linked_manual_bu_out']: @@ -227,8 +205,8 @@ class StockPicking(models.Model): # @api.model # def create(self, vals): # record = super().create(vals) - # if (record.picking_type_code == 'internal' - # and 'BU/PICK/' in record.name + # if (record.picking_type_code == 'internal' + # and 'BU/PICK/' in record.name # and vals.get('linked_manual_bu_out')): # picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) # picking.state_packing = 'packing_done' @@ -365,7 +343,6 @@ class StockPicking(models.Model): except ValueError: return False - def action_get_kgx_pod(self, shipment=False): self.ensure_one() @@ -533,7 +510,7 @@ class StockPicking(models.Model): # rts_days = rts.days # rts_hours = divmod(rts.seconds, 3600) - # estimated_by_erts = rts.total_seconds() / 3600 + # estimated_by_erts = rts.total_seconds() / 3600 # record.countdown_ready_to_ship = f"{rts_days} days, {rts_hours} hours" # record.countdown_hours = estimated_by_erts @@ -1086,7 +1063,8 @@ 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 ('BU/INPUT' not in self.name or 'BU/PUT' not in self.name): + 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) ) @@ -1102,18 +1080,10 @@ class StockPicking(models.Model): return res - def ask_approval(self): - # if self.env.user.is_accounting: - # if self.env.user.is_accounting and self.location_id.id == 57 or self.location_id == 57 and self.approval_status in ['pengajuan1', ''] and 'BU/IU' in self.name and self.approval_status == 'pengajuan1': - # raise UserError("Bisa langsung set ke approval logistik") - if self.env.user.is_accounting and self.approval_status == "pengajuan2" and 'BU/IU' in self.name: - raise UserError("Tidak perlu ask approval sudah approval logistik") - if self.env.user.is_logistic_approver and self.location_id.id == 57 or self.location_id== 57 and self.approval_status == 'pengajuan2' and 'BU/IU' in self.name: + if self.env.user.is_accounting: raise UserError("Bisa langsung Validate") - - # for calendar distribute only # if self.is_internal_use: # stock_move_lines = self.env['stock.move.line'].search([ @@ -1136,7 +1106,6 @@ class StockPicking(models.Model): raise UserError("Qty tidak boleh 0") pick.approval_status = 'pengajuan1' - def ask_receipt_approval(self): if self.env.user.is_logistic_approver: raise UserError('Bisa langsung validate tanpa Ask Receipt') @@ -1338,10 +1307,7 @@ class StockPicking(models.Model): if self.picking_type_id.code == 'incoming' and self.group_id.id == False and self.is_internal_use == False: raise UserError(_('Tidak bisa Validate jika tidak dari Document SO / PO')) - if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57 and self.approval_status == 'pengajuan2': - raise UserError("Harus di Approve oleh Logistik") - - if self.is_internal_use and not self.env.user.is_accounting and self.approval_status == 'pengajuan1' and self.location_id.id == 57: + if self.is_internal_use and not self.env.user.is_accounting: raise UserError("Harus di Approve oleh Accounting") if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: @@ -1350,10 +1316,8 @@ class StockPicking(models.Model): if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager: raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara") - if self.is_internal_use and self.approval_status == 'pengajuan2': + if self.is_internal_use: self.approval_status = 'approved' - elif self.is_internal_use and self.approval_status in ['pengajuan1', '', False]: - raise UserError("Tidak Bisa Validate, set approval status ke approval logistik terlebih dahhulu") elif self.picking_type_id.code == 'incoming': self.approval_receipt_status = 'approved' @@ -1369,7 +1333,6 @@ 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 if self.location_dest_id.id == 58 and 'BU/INPUT/' in self.name: for move in self.move_ids_without_package: @@ -1390,14 +1353,14 @@ class StockPicking(models.Model): if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name: self.check_koli() res = super(StockPicking, self).button_validate() - + # Penambahan link PO di Stock Journal untuk Picking BD for picking in self: if picking.name and 'BD/' in picking.name and picking.purchase_id: stock_journal = self.env['account.move'].search([ ('ref', 'ilike', picking.name + '%'), - ('journal_id', '=', 3) # Stock Journal ID - ], limit = 1) + ('journal_id', '=', 3) # Stock Journal ID + ], limit=1) if stock_journal: stock_journal.write({ 'purchase_order_id': picking.purchase_id.id @@ -1770,7 +1733,8 @@ class StockPicking(models.Model): 'name': move_line.product_id.name, 'code': move_line.product_id.default_code, 'qty': move_line.qty_done, - 'image': self.env['ir.attachment'].api_image('product.template', 'image_128', move_line.product_id.product_tmpl_id.id), + 'image': self.env['ir.attachment'].api_image('product.template', 'image_128', + move_line.product_id.product_tmpl_id.id), }) response = { @@ -2136,7 +2100,6 @@ class CheckProduct(models.Model): _order = 'picking_id, id' _inherit = ['barcodes.barcode_events_mixin'] - picking_id = fields.Many2one( 'stock.picking', string='Picking Reference', @@ -2589,8 +2552,6 @@ class ScanKoli(models.Model): out_move.qty_done += qty_to_assign qty_koli -= qty_to_assign - - def _reset_qty_done_if_no_scan(self, picking_id): product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) -- cgit v1.2.3 From d3fc7f9dfbd3df9687c9531813ac59c3318c6b43 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 6 Oct 2025 09:08:31 +0700 Subject: rollback --- indoteknik_custom/models/stock_picking.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ae7121da..0b91e79d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1080,14 +1080,9 @@ class StockPicking(models.Model): return res - def ask_approval(self): if self.env.user.is_accounting: raise UserError("Bisa langsung Validate") - if self.env.user.is_logistic_approver and self.location_id.id == 57 or self.location_id== 57: - raise UserError("Bisa langsung Validate") - - # for calendar distribute only # if self.is_internal_use: @@ -1110,9 +1105,6 @@ class StockPicking(models.Model): if line.qty_done <= 0: raise UserError("Qty tidak boleh 0") pick.approval_status = 'pengajuan1' - if pick.location_id.id == 57: - pick.approval_status = 'pengajuan2' - return def ask_receipt_approval(self): if self.env.user.is_logistic_approver: @@ -1315,9 +1307,6 @@ class StockPicking(models.Model): if self.picking_type_id.code == 'incoming' and self.group_id.id == False and self.is_internal_use == False: raise UserError(_('Tidak bisa Validate jika tidak dari Document SO / PO')) - if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57: - raise UserError("Harus di Approve oleh Logistik") - if self.is_internal_use and not self.env.user.is_accounting: raise UserError("Harus di Approve oleh Accounting") @@ -1344,7 +1333,6 @@ 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 if self.location_dest_id.id == 58 and 'BU/INPUT/' in self.name: for move in self.move_ids_without_package: @@ -1360,22 +1348,19 @@ class StockPicking(models.Model): ) self.validation_minus_onhand_quantity() - loc = self.location_id - if loc.id == 57 and not self.env.user.is_logistic_approver and self.approval_status in ['pengajuan2']: - raise UserError ("Harus Ask Approval Logistik") self.responsible = self.env.user.id # self.send_koli_to_so() if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name: self.check_koli() res = super(StockPicking, self).button_validate() - + # Penambahan link PO di Stock Journal untuk Picking BD for picking in self: if picking.name and 'BD/' in picking.name and picking.purchase_id: stock_journal = self.env['account.move'].search([ ('ref', 'ilike', picking.name + '%'), - ('journal_id', '=', 3) # Stock Journal ID - ], limit = 1) + ('journal_id', '=', 3) # Stock Journal ID + ], limit=1) if stock_journal: stock_journal.write({ 'purchase_order_id': picking.purchase_id.id -- cgit v1.2.3 From 4603578f08af74760c323aac624ea2ae95252d4b Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 6 Oct 2025 11:02:40 +0700 Subject: cr approval BU/IU --- indoteknik_custom/models/stock_picking.py | 57 ++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 9 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 43e45c09..8408cdc4 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -176,6 +176,28 @@ class StockPicking(models.Model): linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) area_name = fields.Char(string="Area", compute="_compute_area_name") + is_bu_iu = fields.Boolean('Is BU/IU', compute='_compute_is_bu_iu', default=False, copy=False, readonl=True) + + @api.depends('name') + def _compute_is_bu_iu(self): + for record in self: + if 'BU/IU' in record.name: + record.is_bu_iu = True + else: + record.is_bu_iu = False + + def action_bu_iu_to_pengajuan2(self): + for rec in self: + if not rec.is_bu_iu or not rec.is_internal_use: + raise UserError(_("Tombol ini hanya untuk dokumen BU/IU - Internal Use.")) + if rec.approval_status == False: + raise UserError("Harus Ask Approval terlebih dahulu") + if rec.approval_status in ['pengajuan1'] and self.env.user.is_accounting: + rec.approval_status = 'pengajuan2' + # raise UserError(_("Hanya bisa dijalankan saat status di 'pengajuan1'.")) + rec.message_post(body=_("Status naik ke Approval Logistik oleh %s") % self.env.user.display_name) + + return True # def _get_biteship_api_key(self): # # return self.env['ir.config_parameter'].sudo().get_param('biteship.api_key_test') @@ -343,6 +365,7 @@ class StockPicking(models.Model): except ValueError: return False + def action_get_kgx_pod(self, shipment=False): self.ensure_one() @@ -1063,8 +1086,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 ( - 'BU/INPUT' not in self.name or 'BU/PUT' not in self.name): + 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) ) @@ -1080,10 +1102,18 @@ class StockPicking(models.Model): return res + def ask_approval(self): - if self.env.user.is_accounting: + # if self.env.user.is_accounting: + # if self.env.user.is_accounting and self.location_id.id == 57 or self.location_id == 57 and self.approval_status in ['pengajuan1', ''] and 'BU/IU' in self.name and self.approval_status == 'pengajuan1': + # raise UserError("Bisa langsung set ke approval logistik") + if self.env.user.is_accounting and self.approval_status == "pengajuan2" and 'BU/IU' in self.name: + raise UserError("Tidak perlu ask approval sudah approval logistik") + if self.env.user.is_logistic_approver and self.location_id.id == 57 or self.location_id== 57 and self.approval_status == 'pengajuan2' and 'BU/IU' in self.name: raise UserError("Bisa langsung Validate") + + # for calendar distribute only # if self.is_internal_use: # stock_move_lines = self.env['stock.move.line'].search([ @@ -1106,6 +1136,7 @@ class StockPicking(models.Model): raise UserError("Qty tidak boleh 0") pick.approval_status = 'pengajuan1' + def ask_receipt_approval(self): if self.env.user.is_logistic_approver: raise UserError('Bisa langsung validate tanpa Ask Receipt') @@ -1307,7 +1338,10 @@ class StockPicking(models.Model): if self.picking_type_id.code == 'incoming' and self.group_id.id == False and self.is_internal_use == False: raise UserError(_('Tidak bisa Validate jika tidak dari Document SO / PO')) - if self.is_internal_use and not self.env.user.is_accounting: + if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57 and self.approval_status == 'pengajuan2': + raise UserError("Harus di Approve oleh Logistik") + + if self.is_internal_use and not self.env.user.is_accounting and self.approval_status == 'pengajuan1' and self.location_id.id == 57: raise UserError("Harus di Approve oleh Accounting") if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: @@ -1316,8 +1350,10 @@ class StockPicking(models.Model): if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager: raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara") - if self.is_internal_use: + if self.is_internal_use and self.approval_status == 'pengajuan2': self.approval_status = 'approved' + elif self.is_internal_use and self.approval_status in ['pengajuan1', '', False]: + raise UserError("Tidak Bisa Validate, set approval status ke approval logistik terlebih dahhulu") elif self.picking_type_id.code == 'incoming': self.approval_receipt_status = 'approved' @@ -1333,6 +1369,7 @@ 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 if self.location_dest_id.id == 58 and 'BU/INPUT/' in self.name: for move in self.move_ids_without_package: @@ -1359,8 +1396,8 @@ class StockPicking(models.Model): if picking.name and 'BD/' in picking.name and picking.purchase_id: stock_journal = self.env['account.move'].search([ ('ref', 'ilike', picking.name + '%'), - ('journal_id', '=', 3) # Stock Journal ID - ], limit=1) + ('journal_id', '=', 3) # Stock Journal ID + ], limit = 1) if stock_journal: stock_journal.write({ 'purchase_order_id': picking.purchase_id.id @@ -1733,8 +1770,7 @@ class StockPicking(models.Model): 'name': move_line.product_id.name, 'code': move_line.product_id.default_code, 'qty': move_line.qty_done, - 'image': self.env['ir.attachment'].api_image('product.template', 'image_128', - move_line.product_id.product_tmpl_id.id), + 'image': self.env['ir.attachment'].api_image('product.template', 'image_128', move_line.product_id.product_tmpl_id.id), }) response = { @@ -2100,6 +2136,7 @@ class CheckProduct(models.Model): _order = 'picking_id, id' _inherit = ['barcodes.barcode_events_mixin'] + picking_id = fields.Many2one( 'stock.picking', string='Picking Reference', @@ -2552,6 +2589,8 @@ class ScanKoli(models.Model): out_move.qty_done += qty_to_assign qty_koli -= qty_to_assign + + def _reset_qty_done_if_no_scan(self, picking_id): product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) -- cgit v1.2.3 From 8fe189d8f4caeed4b1aff764abc005b4e4ab3979 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 6 Oct 2025 13:59:19 +0700 Subject: change button name and fix wrong validation --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 8408cdc4..be0d77ac 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1338,11 +1338,11 @@ class StockPicking(models.Model): if self.picking_type_id.code == 'incoming' and self.group_id.id == False and self.is_internal_use == False: raise UserError(_('Tidak bisa Validate jika tidak dari Document SO / PO')) - if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57 and self.approval_status == 'pengajuan2': - raise UserError("Harus di Approve oleh Logistik") + # if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57 and self.approval_status == 'pengajuan2': + # raise UserError("Harus di Approve oleh Logistik") - if self.is_internal_use and not self.env.user.is_accounting and self.approval_status == 'pengajuan1' and self.location_id.id == 57: - raise UserError("Harus di Approve oleh Accounting") + if self.is_internal_use and not self.env.user.is_logistic_approver and self.approval_status in ['pengajuan1', 'pengajuan2'] and self.location_id.id == 57: + raise UserError("Harus di Approve oleh Logistik") if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: raise UserError("Harus di Approve oleh Logistik") -- cgit v1.2.3 From f9645a14ad98a901ae66340b87c592f7a0701d01 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 6 Oct 2025 14:09:41 +0700 Subject: add more vals --- indoteknik_custom/models/stock_picking.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index be0d77ac..a9c09ab7 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -194,8 +194,9 @@ class StockPicking(models.Model): raise UserError("Harus Ask Approval terlebih dahulu") if rec.approval_status in ['pengajuan1'] and self.env.user.is_accounting: rec.approval_status = 'pengajuan2' - # raise UserError(_("Hanya bisa dijalankan saat status di 'pengajuan1'.")) rec.message_post(body=_("Status naik ke Approval Logistik oleh %s") % self.env.user.display_name) + if rec.approval_status in ['pengajuan1', 'pengajuan2', ''] and not self.env.user.is_accounting: + raise UserError("Tombol hanya untuk accounting") return True -- cgit v1.2.3 From 540690de9d9da5805671f047151bf1d09d98c07c Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Tue, 7 Oct 2025 10:27:52 +0700 Subject: push --- indoteknik_custom/models/refund_sale_order.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index a866af6e..f9adeeff 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -643,12 +643,14 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 7), ('state', '=', 'posted'), ]) - misc = self.env['account.move'].search([ - ('ref', 'ilike', all_invoices.mapped('name')[0]), - ('ref', 'not ilike', 'reklas'), - ('journal_id', '=', 13), - ('state', '=', 'posted'), - ]) + misc = self.env['account.move'] + if all_invoices: + misc = self.env['account.move'].search([ + ('ref', 'ilike', all_invoices.mapped('name')[0]), + ('ref', 'not ilike', 'reklas'), + ('journal_id', '=', 13), + ('state', '=', 'posted'), + ]) moves2 = self.env['account.move'] if so_ids: so_records = self.env['sale.order'].browse(so_ids) -- cgit v1.2.3 From 13f131132a140e3d3b7f551d19d76a4f32a49f6b Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Tue, 7 Oct 2025 15:06:01 +0700 Subject: push --- indoteknik_custom/models/refund_sale_order.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index f9adeeff..47565dfc 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -304,12 +304,14 @@ class RefundSaleOrder(models.Model): ('state', '=', 'posted'), ]) - misc = self.env['account.move'].search([ - ('ref', 'ilike', invoices.mapped('name')[0]), - ('ref', 'not ilike', 'reklas'), - ('journal_id', '=', 13), - ('state', '=', 'posted'), - ]) + misc = self.env['account.move'] + if invoices: + misc = self.env['account.move'].search([ + ('ref', 'ilike', invoices.mapped('name')[0]), + ('ref', 'not ilike', 'reklas'), + ('journal_id', '=', 13), + ('state', '=', 'posted'), + ]) moves2 = self.env['account.move'] if so_ids: so_names = self.env['sale.order'].browse(so_ids).mapped('name') -- cgit v1.2.3 From 3c7176c6d87c4a1385e5a87e119878b6e9a5d88f Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 7 Oct 2025 15:08:17 +0700 Subject: add more vals IU approval --- indoteknik_custom/models/stock_picking.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a2bd7339..51b6276c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1340,11 +1340,14 @@ class StockPicking(models.Model): raise UserError(_('Tidak bisa Validate jika tidak dari Document SO / PO')) # if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57 and self.approval_status == 'pengajuan2': - # raise UserError("Harus di Approve oleh Logistik") + # raise UserError("Harus di Approve oleh Logistik") - if self.is_internal_use and not self.env.user.is_logistic_approver and self.approval_status in ['pengajuan1', 'pengajuan2'] and self.location_id.id == 57: + if self.is_internal_use and not self.env.user.is_logistic_approver and self.approval_status in ['pengajuan1', 'pengajuan2'] and self.location_id.id == 57 and self.is_bu_iu == True and 'BU/IU' in self.name: raise UserError("Harus di Approve oleh Logistik") + if self.is_internal_use and not self.env.user.is_accounting and self.approval_status in ['pengajuan1', '', False] and self.is_bu_iu == False: + raise UserError("Harus di Approve oleh Accounting") + if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: raise UserError("Harus di Approve oleh Logistik") @@ -1353,7 +1356,7 @@ class StockPicking(models.Model): if self.is_internal_use and self.approval_status == 'pengajuan2': self.approval_status = 'approved' - elif self.is_internal_use and self.approval_status in ['pengajuan1', '', False]: + elif self.is_internal_use and self.approval_status in ['pengajuan1', '', False] and 'BU/IU' in self.name and self.is_bu_iu == True: raise UserError("Tidak Bisa Validate, set approval status ke approval logistik terlebih dahhulu") elif self.picking_type_id.code == 'incoming': self.approval_receipt_status = 'approved' -- cgit v1.2.3 From 92d28be458ca53c9994a175225232b24c7fde283 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 7 Oct 2025 15:20:29 +0700 Subject: fix approval IU --- indoteknik_custom/models/stock_picking.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 51b6276c..4772c433 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1342,7 +1342,10 @@ class StockPicking(models.Model): # if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57 and self.approval_status == 'pengajuan2': # raise UserError("Harus di Approve oleh Logistik") - if self.is_internal_use and not self.env.user.is_logistic_approver and self.approval_status in ['pengajuan1', 'pengajuan2'] and self.location_id.id == 57 and self.is_bu_iu == True and 'BU/IU' in self.name: + if self.is_internal_use and self.approval_status in ['pengajuan1', '', False] and 'BU/IU' in self.name and self.is_bu_iu == True: + raise UserError("Tidak Bisa Validate, set approval status ke approval logistik terlebih dahhulu") + + if self.is_internal_use and not self.env.user.is_logistic_approver and self.approval_status in ['pengajuan2'] and self.is_bu_iu == True and 'BU/IU' in self.name: raise UserError("Harus di Approve oleh Logistik") if self.is_internal_use and not self.env.user.is_accounting and self.approval_status in ['pengajuan1', '', False] and self.is_bu_iu == False: @@ -1356,8 +1359,6 @@ class StockPicking(models.Model): if self.is_internal_use and self.approval_status == 'pengajuan2': self.approval_status = 'approved' - elif self.is_internal_use and self.approval_status in ['pengajuan1', '', False] and 'BU/IU' in self.name and self.is_bu_iu == True: - raise UserError("Tidak Bisa Validate, set approval status ke approval logistik terlebih dahhulu") elif self.picking_type_id.code == 'incoming': self.approval_receipt_status = 'approved' -- cgit v1.2.3 From 07ae0b7f2eab52daf5e33dc47450189537be2e1c Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Tue, 7 Oct 2025 18:20:53 +0700 Subject: (andri) email cc surat piutang --- indoteknik_custom/models/letter_receivable.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index ffe14491..821825d6 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -193,11 +193,11 @@ class SuratPiutang(models.Model): line.amount_residual or 0.0 for line in rec.line_ids if line.selected ) - @api.constrains("tujuan_email") - def _check_email_format(self): - for rec in self: - if rec.tujuan_email and not mail.single_email_re.match(rec.tujuan_email): - raise ValidationError(_("Format email tidak valid: %s") % rec.tujuan_email) + # @api.constrains("tujuan_email") + # def _check_email_format(self): + # for rec in self: + # if rec.tujuan_email and not mail.single_email_re.match(rec.tujuan_email): + # raise ValidationError(_("Format email tidak valid: %s") % rec.tujuan_email) def action_approve(self): wib = pytz.timezone('Asia/Jakarta') @@ -363,6 +363,7 @@ class SuratPiutang(models.Model): 'email_to': self.tujuan_email, 'email_from': 'finance@indoteknik.co.id', # 'email_cc': ",".join(sorted(set(cc_list))), + 'email_cc': 'finance@indoteknik.co.id', 'body_html': body_html, # Menggunakan body_html yang sudah ditentukan di atas 'attachments': [(attachment.name, attachment.datas)], # 'reply_to': 'finance@indoteknik.co.id', -- cgit v1.2.3 From ec5053921c0e858a4c33fb2cddf1152600831f7a Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Tue, 7 Oct 2025 18:25:04 +0700 Subject: (andri) fix --- indoteknik_custom/models/letter_receivable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 821825d6..f75af92e 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -362,11 +362,11 @@ class SuratPiutang(models.Model): 'subject': subject, # Menggunakan subject yang sudah ditentukan di atas 'email_to': self.tujuan_email, 'email_from': 'finance@indoteknik.co.id', - # 'email_cc': ",".join(sorted(set(cc_list))), + 'email_cc': ",".join(sorted(set(cc_list))), 'email_cc': 'finance@indoteknik.co.id', 'body_html': body_html, # Menggunakan body_html yang sudah ditentukan di atas 'attachments': [(attachment.name, attachment.datas)], - # 'reply_to': 'finance@indoteknik.co.id', + 'reply_to': 'finance@indoteknik.co.id', } template.with_context(mail_post_autofollow=False).send_mail( -- cgit v1.2.3 From 776bc2d9f132050ceed95ef2a96eb1dcf8ad1c44 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Tue, 7 Oct 2025 18:44:12 +0700 Subject: (andri) add button select & unselect all --- indoteknik_custom/models/letter_receivable.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index f75af92e..370e0909 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -69,6 +69,18 @@ class SuratPiutang(models.Model): "sp1": "sp2", "sp2": "sp3", } + + def action_select_all_lines(self): + for rec in self: + if not rec.line_ids: + raise UserError(_("Tidak ada invoice line untuk dipilih.")) + rec.line_ids.write({'selected': True}) + + def action_unselect_all_lines(self): + for rec in self: + if not rec.line_ids: + raise UserError(_("Tidak ada invoice line untuk dihapus seleksinya.")) + rec.line_ids.write({'selected': False}) @api.onchange('partner_id') def _onchange_partner_id_domain(self): -- cgit v1.2.3 From 861879ce8479b22bd340eaa3835c8682810866dd Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Tue, 7 Oct 2025 18:53:24 +0700 Subject: (andri) fix --- indoteknik_custom/models/letter_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 370e0909..f9f65b65 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -375,7 +375,7 @@ class SuratPiutang(models.Model): 'email_to': self.tujuan_email, 'email_from': 'finance@indoteknik.co.id', 'email_cc': ",".join(sorted(set(cc_list))), - 'email_cc': 'finance@indoteknik.co.id', + # 'email_cc': 'finance@indoteknik.co.id', # testing 'body_html': body_html, # Menggunakan body_html yang sudah ditentukan di atas 'attachments': [(attachment.name, attachment.datas)], 'reply_to': 'finance@indoteknik.co.id', -- cgit v1.2.3 From adafea307399d897df586fe13029c92efcdb8e80 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 7 Oct 2025 19:48:39 +0700 Subject: multiple chat bubble SJ Tele --- indoteknik_custom/models/sj_tele.py | 73 +++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 40 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index d44aa338..5ea08340 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -19,41 +19,18 @@ class SjTele(models.Model): create_date = fields.Datetime(string='Create Date') date_doc_kirim = fields.Datetime(string='Tanggal Kirim SJ') - # @api.model - # def run_pentaho_carte(self): - # carte = "http://127.0.0.1:8080" - # job_kjb = r"C:/Users/Indoteknik/Desktop/tes.kjb" - # params = {"job": job_kjb, "level": "Basic", "block": "Y"} - # try: - # r = requests.get( - # f"{carte}/kettle/executeJob/", - # params=params, - # auth=("cluster", "cluster"), - # timeout=900, - # ) - # r.raise_for_status() - # # kalau Carte mengembalikan ERROR, anggap gagal - # if "ERROR" in r.text: - # raise UserError(f"Carte error: {r.text}") - # except Exception as e: - # _logger.exception("Carte call failed: %s", e) - # raise UserError(f"Gagal memanggil Carte: {e}") - - # time.sleep(3) - - # self.env['sj.tele'].sudo().woi() - - # return True - def woi(self): bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' chat_id_mqdd = '-1003087280519' api_base = f'https://api.telegram.org/bot{bot_mqdd}' + # bot_testing = '8306689189:AAHEFe5xwAkapoQ8xKoNZs-6gVfv3kO3kaU' + # chat_id_testing = '-4920864331' + # api_testing = f'https://api.telegram.org/bot{bot_testing}' - data = self.search([], order='create_date asc', limit=15) + data = self.search([], order='create_date asc') if not data: - text = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\nโœ… tidak ada data (semua sudah tercatat)." + text = "โœ… tidak ada data (semua sudah tercatat)." try: r = requests.post(api_base + "/sendMessage", json={'chat_id': chat_id_mqdd, 'text': text}, @@ -74,7 +51,6 @@ class SjTele(models.Model): dttm = (rec.picking_id.date_doc_kirim if (rec.picking_id and rec.picking_id.date_doc_kirim) else getattr(rec, 'date_doc_kirim', None)) - # format header tanggal (string), tanpa konversi Waktu/WIB if dttm: date_header = dttm if isinstance(dttm, str) else fields.Datetime.to_string(dttm) date_header = date_header[:10] @@ -84,19 +60,36 @@ class SjTele(models.Model): if name: groups.setdefault(date_header, []).append(f"- ({pid}) - {name} - {so}") - # build output berurutan per tanggal for header_date, items in groups.items(): lines.append(header_date) lines.extend(items) - header = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n" - text = header + "\n".join(lines) - - try: - r = requests.post(api_base + "/sendMessage", - json={'chat_id': chat_id_mqdd, 'text': text}) - r.raise_for_status() - except Exception as e: - _logger.exception("Gagal kirim Telegram: %s", e) - return True \ No newline at end of file + BUB = 20 # jumlah baris per bubble + + for i in range(0, len(lines), BUB): + body = "\n".join(lines[i:i + BUB]) + text = header + body + try: + r = requests.post( + api_base + "/sendMessage", + json={'chat_id': chat_id_mqdd, 'text': text}, + timeout=20 + ) + r.raise_for_status() + except Exception as e: + _logger.exception("Gagal kirim Telegram (batch %s-%s): %s", i + 1, min(i + BUB, len(lines)), e) + time.sleep(5) # jeda kecil biar rapi & aman rate limit + + return True + + # header = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n" + # text = header + "\n".join(lines) + # + # try: + # r = requests.post(api_base + "/sendMessage", + # json={'chat_id': chat_id_mqdd, 'text': text}) + # r.raise_for_status() + # except Exception as e: + # _logger.exception("Gagal kirim Telegram: %s", e) + # return True \ No newline at end of file -- cgit v1.2.3 From d72c8fb9cd97552ef15aee0b70f7e1248b4c4696 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 8 Oct 2025 10:00:56 +0700 Subject: constrain delivery departure date to denise and faishal --- indoteknik_custom/models/stock_picking.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4772c433..217e76cb 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -178,6 +178,14 @@ class StockPicking(models.Model): area_name = fields.Char(string="Area", compute="_compute_area_name") is_bu_iu = fields.Boolean('Is BU/IU', compute='_compute_is_bu_iu', default=False, copy=False, readonl=True) + @api.constrains('driver_departure_date') + def _constrains_driver_departure_date(self): + allowed_user_ids = [17, 6277, 25] + for record in self: + if record.driver_departure_date and self.env.user.id not in allowed_user_ids: + raise UserError("Hanya Denise dan Faishal yang dapat mengubah Delivery Departure Date.") + + @api.depends('name') def _compute_is_bu_iu(self): for record in self: -- cgit v1.2.3 From 51155d702da715c82ea55322ee049d9b1ee34faa Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Wed, 8 Oct 2025 11:43:57 +0700 Subject: (andri) fix approval --- indoteknik_custom/models/letter_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index f9f65b65..a98e46a1 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -215,7 +215,7 @@ class SuratPiutang(models.Model): wib = pytz.timezone('Asia/Jakarta') now_wib = datetime.now(wib) - sales_manager_ids = [10] # ganti dengan ID user Sales Manager + sales_manager_ids = [19] # ganti dengan ID user Sales Manager pimpinan_user_ids = [7] # ganti dengan ID user Pimpinan for rec in self: -- cgit v1.2.3 From c5642f4f6c4f0969475d863bee7243a83b9290dc Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 8 Oct 2025 14:55:04 +0700 Subject: partial --- indoteknik_custom/models/__init__.py | 2 + indoteknik_custom/models/automatic_purchase.py | 161 +++++++++++---------- indoteknik_custom/models/domain_apo.py | 12 ++ indoteknik_custom/models/partial_delivery.py | 189 +++++++++++++++++++++++++ indoteknik_custom/models/sale_order.py | 13 +- indoteknik_custom/models/stock_move.py | 1 + 6 files changed, 298 insertions(+), 80 deletions(-) create mode 100644 indoteknik_custom/models/domain_apo.py create mode 100644 indoteknik_custom/models/partial_delivery.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 6dc61277..5ac4d6ca 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -160,3 +160,5 @@ from . import update_date_planned_po_wizard from . import unpaid_invoice_view from . import letter_receivable from . import sj_tele +from . import partial_delivery +from . import domain_apo diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 83a7cb3c..d9ec17f4 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -93,7 +93,7 @@ class AutomaticPurchase(models.Model): counter = 0 for vendor in vendor_ids: - self.create_po_by_vendor(vendor['partner_id'][0]) + self.create_po_for_vendor(vendor['partner_id'][0]) # param_header = { # 'partner_id': vendor['partner_id'][0], @@ -191,95 +191,106 @@ class AutomaticPurchase(models.Model): if qty_pj > qty_outgoing_pj: raise UserError('Qty yang anda beli lebih dari qty outgoing. %s' %id_po) - def create_po_by_vendor(self, vendor_id): + def create_po_for_vendor(self, vendor_id): current_time = datetime.now() name = "/PJ/" if not self.apo_type == 'reordering' else "/A/" - PRODUCT_PER_PO = 20 auto_purchase_line = self.env['automatic.purchase.line'] - # Domain untuk semua baris dengan vendor_id tertentu - domain = [ + config = self.env['apo.domain.config'].search([ + ('vendor_id', '=', vendor_id) + ], limit=1) + + base_domain = [ ('automatic_purchase_id', '=', self.id), ('partner_id', '=', vendor_id), ('qty_purchase', '>', 0) ] - # Tambahkan domain khusus untuk brand_id 22 dan 564 - special_brand_domain = domain + [('brand_id', 'in', [22, 564])] - regular_domain = domain + [('brand_id', 'not in', [22, 564])] - - # Fungsi untuk membuat PO berdasarkan domain tertentu - def create_po_for_domain(domain, special_payment_term=False): - products_len = auto_purchase_line.search_count(domain) - page = math.ceil(products_len / PRODUCT_PER_PO) - - for i in range(page): - # Buat PO baru - param_header = { - 'partner_id': vendor_id, - 'currency_id': 12, - 'user_id': self.env.user.id, - 'company_id': 1, # indoteknik dotcom gemilang - 'picking_type_id': 28, # indoteknik bandengan receipts - 'date_order': current_time, - 'from_apo': True, - 'note_description': 'Automatic PO' - } + # Kalau vendor punya brand spesial โ†’ bikin domain sesuai config + if config and config.is_special: + special_brand_domain = base_domain + [('brand_id', 'in', config.brand_ids.ids)] + self._create_po_for_domain( + vendor_id, special_brand_domain, name, PRODUCT_PER_PO, current_time, config.payment_term_id, special=config.is_special + ) - new_po = self.env['purchase.order'].create([param_header]) + # Regular domain (selain brand spesial) + regular_domain = base_domain + if config and config.is_special and config.brand_ids: + regular_domain = base_domain + [('brand_id', 'not in', config.brand_ids.ids)] - # Set payment_term_id khusus jika diperlukan - if special_payment_term: - new_po.payment_term_id = 29 - else: - new_po.payment_term_id = new_po.partner_id.property_supplier_payment_term_id + self._create_po_for_domain( + vendor_id, regular_domain, name, PRODUCT_PER_PO, current_time, config.payment_term_id + ) - new_po.name = new_po.name + name + str(i + 1) - self.env['automatic.purchase.match'].create([{ - 'automatic_purchase_id': self.id, - 'order_id': new_po.id - }]) + def _create_po_for_domain(self, vendor_id, domain, name, PRODUCT_PER_PO, current_time, payment_term_id, special=False): + auto_purchase_line = self.env['automatic.purchase.line'] + products_len = auto_purchase_line.search_count(domain) + page = math.ceil(products_len / PRODUCT_PER_PO) + + for i in range(page): + # Buat header PO + param_header = { + 'partner_id': vendor_id, + 'currency_id': 12, + 'user_id': self.env.user.id, + 'company_id': 1, + 'picking_type_id': 28, + 'date_order': current_time, + 'from_apo': True, + 'note_description': 'Automatic PO' + } + new_po = self.env['purchase.order'].create(param_header) + + # Set payment term + new_po.payment_term_id = payment_term_id.id if special else ( + new_po.partner_id.property_supplier_payment_term_id + ) + + new_po.name = new_po.name + name + str(i + 1) + + self.env['automatic.purchase.match'].create([{ + 'automatic_purchase_id': self.id, + 'order_id': new_po.id + }]) + + # Ambil lines + lines = auto_purchase_line.search( + domain, + offset=i * PRODUCT_PER_PO, + limit=PRODUCT_PER_PO + ) + + # Pre-fetch sales_match biar ga search per line + sales_matches = self.env['automatic.purchase.sales.match'].search([ + ('automatic_purchase_id', '=', self.id), + ('product_id', 'in', lines.mapped('product_id').ids), + ]) + match_map = {sm.product_id.id: sm for sm in sales_matches} + + for line in lines: + product = line.product_id + sales_match = match_map.get(product.id) + param_line = { + 'order_id': new_po.id, + 'product_id': product.id, + 'product_qty': line.qty_purchase, + 'qty_available_store': product.qty_available_bandengan, + 'suggest': product._get_po_suggest(line.qty_purchase), + 'product_uom_qty': line.qty_purchase, + 'price_unit': line.last_price, + 'ending_price': line.last_price, + 'taxes_id': [(6, 0, [line.taxes_id.id])] if line.taxes_id else False, + 'so_line_id': sales_match.sale_line_id.id if sales_match else None, + 'so_id': sales_match.sale_id.id if sales_match else None + } + new_po_line = self.env['purchase.order.line'].create(param_line) + line.current_po_id = new_po.id + line.current_po_line_id = new_po_line.id + + self.create_purchase_order_sales_match(new_po) - # Ambil baris sesuai halaman - lines = auto_purchase_line.search( - domain, - offset=i * PRODUCT_PER_PO, - limit=PRODUCT_PER_PO - ) - - for line in lines: - product = line.product_id - sales_match = self.env['automatic.purchase.sales.match'].search([ - ('automatic_purchase_id', '=', self.id), - ('product_id', '=', product.id), - ]) - param_line = { - 'order_id': new_po.id, - 'product_id': product.id, - 'product_qty': line.qty_purchase, - 'qty_available_store': product.qty_available_bandengan, - 'suggest': product._get_po_suggest(line.qty_purchase), - 'product_uom_qty': line.qty_purchase, - 'price_unit': line.last_price, - 'ending_price': line.last_price, - 'taxes_id': [line.taxes_id.id] if line.taxes_id else None, - 'so_line_id': sales_match[0].sale_line_id.id if sales_match else None, - 'so_id': sales_match[0].sale_id.id if sales_match else None - } - new_po_line = self.env['purchase.order.line'].create([param_line]) - line.current_po_id = new_po.id - line.current_po_line_id = new_po_line.id - - self.create_purchase_order_sales_match(new_po) - - # Buat PO untuk special brand - if vendor_id == 23: - create_po_for_domain(special_brand_domain, special_payment_term=True) - - # Buat PO untuk regular domain - create_po_for_domain(regular_domain, "") def update_purchase_price_so_line(self, apo): diff --git a/indoteknik_custom/models/domain_apo.py b/indoteknik_custom/models/domain_apo.py new file mode 100644 index 00000000..585dd24c --- /dev/null +++ b/indoteknik_custom/models/domain_apo.py @@ -0,0 +1,12 @@ +from odoo import models, fields + + +class ApoDomainConfig(models.Model): + _name = 'apo.domain.config' + _description = 'Automatic Purchase Domain Config' + + name = fields.Char(string="Config Name", required=True) + vendor_id = fields.Many2one('res.partner', string="Vendor", required=True, domain=[('supplier_rank', '>', 0)]) + brand_ids = fields.Many2many('x_manufactures', string="Special Brands") + payment_term_id = fields.Many2one('account.payment.term', string="Payment Term") + is_special = fields.Boolean(string="Special Vendor?", default=False) diff --git a/indoteknik_custom/models/partial_delivery.py b/indoteknik_custom/models/partial_delivery.py new file mode 100644 index 00000000..c9d2ba5c --- /dev/null +++ b/indoteknik_custom/models/partial_delivery.py @@ -0,0 +1,189 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import UserError, ValidationError +from datetime import datetime, timedelta, timezone, time +import logging, random, string, requests, math, json, re, qrcode, base64 + +_logger = logging.getLogger(__name__) + +class PartialDeliveryWizard(models.TransientModel): + _name = 'partial.delivery.wizard' + _description = 'Partial Delivery Wizard' + + sale_id = fields.Many2one('sale.order') + picking_ids = fields.Many2many('stock.picking') + picking_id = fields.Many2one( + 'stock.picking', + string='Delivery Order', + domain="[('id','in',picking_ids), ('state', 'not in', ('done', 'cancel')), ('name', 'like', 'BU/PICK/%')]" + ) + line_ids = fields.One2many('partial.delivery.wizard.line', 'wizard_id') + + # @api.model + # def default_get(self, fields_list): + # res = super().default_get(fields_list) + # picking_ids_ctx = self.env.context.get('default_picking_ids') + # lines = [] + # if picking_ids_ctx: + # if isinstance(picking_ids_ctx, list) and picking_ids_ctx and isinstance(picking_ids_ctx[0], tuple): + # picking_ids = picking_ids_ctx[0][2] + # else: + # picking_ids = picking_ids_ctx + + # pickings = self.env['stock.picking'].browse(picking_ids) + # moves = pickings.move_ids_without_package.filtered(lambda m: m.reserved_availability > 0) + + # for move in moves: + # lines.append((0, 0, { + # 'product_id': move.product_id.id, + # 'reserved_qty': move.reserved_availability, + # 'move_id': move.id, + # })) + # res['line_ids'] = lines + # return res + + @api.onchange('picking_id') + def _onchange_picking_id(self): + """Generate lines whenever picking_id is changed""" + lines = [] + if self.picking_id: + moves = self.picking_id.move_ids_without_package.filtered(lambda m: m.reserved_availability > 0) + for move in moves: + lines.append((0, 0, { + 'product_id': move.product_id.id, + 'reserved_qty': move.reserved_availability, + 'move_id': move.id, + })) + self.line_ids = lines + + + def action_confirm_partial_delivery(self): + self.ensure_one() + StockPicking = self.env['stock.picking'] + + picking = self.picking_id + if not picking: + raise UserError(_("Tidak ada picking yang dipilih.")) + + if picking.state != "assigned": + raise UserError(_("Picking harus dalam status Ready (assigned).")) + + + lines_by_qty = self.line_ids.filtered(lambda l: l.selected_qty > 0) + lines_by_selected = self.line_ids.filtered(lambda l: l.selected and not l.selected_qty) + selected_lines = lines_by_qty | lines_by_selected # gabung dua domain hasil filter + + if not selected_lines: + raise UserError(_("Tidak ada produk yang dipilih atau diisi jumlahnya.")) + + if selected_lines.selected_qty > selected_lines.reserved_qty: + raise UserError(_("Jumlah produk yang dipilih melebihi jumlah reserved.")) + + new_picking = StockPicking.create({ + 'origin': picking.origin, + 'partner_id': picking.partner_id.id, + 'picking_type_id': picking.picking_type_id.id, + 'location_id': picking.location_id.id, + 'location_dest_id': picking.location_dest_id.id, + 'company_id': picking.company_id.id, + 'state_reserve': 'partial', + }) + + for line in selected_lines: + move = line.move_id + move._do_unreserve() + + # kalau cuma selected tanpa isi qty, otomatis set selected_qty = reserved_qty + if line.selected and not line.selected_qty: + line.selected_qty = line.reserved_qty + + # MODE 1 โ†’ Prioritas kalau ada selected_qty + if line.selected_qty > 0: + if line.selected_qty > move.product_uom_qty: + raise UserError(_( + f"Qty kirim ({line.selected_qty}) untuk {move.product_id.display_name} melebihi qty move ({move.product_uom_qty})." + )) + + if line.selected_qty < move.product_uom_qty: + qty_to_keep = move.product_uom_qty - line.selected_qty + # split move + new_move = move.copy(default={ + 'product_uom_qty': line.selected_qty, + 'picking_id': new_picking.id, + 'partial': True, + }) + move.write({'product_uom_qty': qty_to_keep}) + else: + # full pindah + move.write({'picking_id': new_picking.id, 'partial': True}) + + + + # Confirm & assign DO baru + new_picking.action_confirm() + new_picking.action_assign() + + # Reassign DO lama biar sisa qty ke-update + picking.action_assign() + + # --- ๐Ÿ”ข Rename picking baru dengan format "/(Nomor urut)" --- + existing_partials = self.env['stock.picking'].search([ + ('origin', '=', picking.origin), + ('state_reserve', '=', 'partial'), + ('id', '!=', new_picking.id), + ], order='name asc') + + suffix_number = len(existing_partials) + if suffix_number == 0: + suffix_number = 1 + else: + suffix_number += 1 + + new_name = f"{picking.name}/{suffix_number}" + new_picking.name = new_name + + # --- ๐Ÿ’ฌ Post message ke SO --- + if picking.origin: + sale_order = self.env['sale.order'].search([('name', '=', picking.origin)], limit=1) + if sale_order: + sale_order.message_post( + body=f"Partial Delivery Created: {new_picking.name} " + f"oleh {self.env.user.name}", + message_type="comment", + subtype_xmlid="mail.mt_note", + ) + + # --- ๐Ÿ“ Log di DO baru --- + new_picking.message_post( + body=f"Partial Picking created dari {picking.name} oleh {self.env.user.name}", + message_type="comment", + subtype_xmlid="mail.mt_note", + ) + + return { + "type": "ir.actions.act_window", + "res_model": "stock.picking", + "view_mode": "form", + "res_id": new_picking.id, + "target": "current", + "effect": { + "fadeout": "slow", + "message": f"๐Ÿšš Partial Delivery {new_picking.name} berhasil dibuat!", + "type": "rainbow_man", + }, + } + + + +class PartialDeliveryWizardLine(models.TransientModel): + _name = 'partial.delivery.wizard.line' + _description = 'Partial Delivery Wizard Line' + + wizard_id = fields.Many2one('partial.delivery.wizard') + product_id = fields.Many2one('product.product', string="Product") + reserved_qty = fields.Float(string="Reserved Qty") + selected_qty = fields.Float(string="Send Qty") + move_id = fields.Many2one('stock.move') + selected = fields.Boolean(string="Select") + sale_line_id = fields.Many2one('sale.order.line', string="SO Line", related='move_id.sale_line_id') + ordered_qty = fields.Float(related='sale_line_id.product_uom_qty', string="Ordered Qty") + diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 3aaae12d..57217894 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1813,10 +1813,10 @@ class SaleOrder(models.Model): # rec.commitment_date = rec.expected_ready_to_ship - @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship + @api.onchange('expected_ready_to_ship') def _onchange_expected_ready_ship_date(self): self._validate_expected_ready_ship_date() - + def _set_etrts_date(self): for order in self: if order.state in ('done', 'cancel', 'sale'): @@ -2241,7 +2241,7 @@ class SaleOrder(models.Model): raise UserError("Payment Term pada Master Data Customer harus diisi") if not partner.active_limit and term_days > 0: raise UserError("Credit Limit pada Master Data Customer harus diisi") - if order.payment_term_id != partner.property_payment_term_id: + if order.payment_term_id != partner.property_payment_term_id and not order.partner_id.id == 29179: raise UserError("Payment Term berbeda pada Master Data Customer") if (partner.customer_type == 'pkp' or order.customer_type == 'pkp') and order.npwp != partner.npwp: raise UserError("NPWP berbeda pada Master Data Customer") @@ -2324,7 +2324,7 @@ class SaleOrder(models.Model): raise UserError("Payment Term pada Master Data Customer harus diisi") if not partner.active_limit and term_days > 0: raise UserError("Credit Limit pada Master Data Customer harus diisi") - if order.payment_term_id != partner.property_payment_term_id: + if order.payment_term_id != partner.property_payment_term_id and not order.partner_id.id == 29179: raise UserError("Payment Term berbeda pada Master Data Customer") if (partner.customer_type == 'pkp' or order.customer_type == 'pkp') and order.npwp != partner.npwp: raise UserError("NPWP berbeda pada Master Data Customer") @@ -3328,12 +3328,15 @@ class SaleOrder(models.Model): for order in self: partner = order.partner_id.parent_id or order.partner_id customer_payment_term = partner.property_payment_term_id - if vals['payment_term_id'] != customer_payment_term.id: + if vals['payment_term_id'] != customer_payment_term.id and not order.partner_id.id == 29179: raise UserError( f"Payment Term berbeda pada Master Data Customer. " f"Harap ganti ke '{customer_payment_term.name}' " f"sesuai dengan payment term yang terdaftar pada customer." ) + + if order.partner_id.id == 29179 and vals['payment_term_id'] not in [25,28]: + raise UserError(_("Pilih payment term 60 hari atau 30 hari.")) res = super(SaleOrder, self).write(vals) diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index d6505a86..b7db8775 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -19,6 +19,7 @@ class StockMove(models.Model): vendor_id = fields.Many2one('res.partner' ,string='Vendor') hold_outgoingg = fields.Boolean('Hold Outgoing', default=False) product_image = fields.Binary(related="product_id.image_128", string="Product Image", readonly=True) + partial = fields.Boolean('Partial?', default=False) # @api.model_create_multi # def create(self, vals_list): -- cgit v1.2.3 From f71f148d00b546bc46922eeeeaa512f79cecd573 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 8 Oct 2025 15:04:22 +0700 Subject: balikin sp --- indoteknik_custom/models/stock_picking.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 217e76cb..4772c433 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -178,14 +178,6 @@ class StockPicking(models.Model): area_name = fields.Char(string="Area", compute="_compute_area_name") is_bu_iu = fields.Boolean('Is BU/IU', compute='_compute_is_bu_iu', default=False, copy=False, readonl=True) - @api.constrains('driver_departure_date') - def _constrains_driver_departure_date(self): - allowed_user_ids = [17, 6277, 25] - for record in self: - if record.driver_departure_date and self.env.user.id not in allowed_user_ids: - raise UserError("Hanya Denise dan Faishal yang dapat mengubah Delivery Departure Date.") - - @api.depends('name') def _compute_is_bu_iu(self): for record in self: -- cgit v1.2.3 From c8b3d4f3c5da81eb3858af6b8a7fc54b83a286a5 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 8 Oct 2025 15:20:43 +0700 Subject: delivery departure date val --- indoteknik_custom/models/stock_picking.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4772c433..8e947267 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1654,6 +1654,11 @@ class StockPicking(models.Model): line.sale_line_id = sale_line.id def write(self, vals): + allowed_user_ids = [17, 6277] + if 'driver_departure_date' in vals: + if self.env.user.id not in allowed_user_ids and not self.env.context.get('allow_driver_departure_edit'): + raise UserError("Hanya Denise dan Faisal yang dapat mengubah Delivery Departure Date.") + if 'linked_manual_bu_out' in vals: for record in self: if (record.picking_type_code == 'internal' -- cgit v1.2.3 From 7b17ded8576c86470c56e5ccf961d0378b3a820f Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 8 Oct 2025 15:24:43 +0700 Subject: remove approval status api SO --- indoteknik_custom/models/sale_order.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 57217894..4a7203a1 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1984,10 +1984,10 @@ class SaleOrder(models.Model): # raise UserError('Kelurahan Real Delivery Address harus diisi') def generate_payment_link_midtrans_sales_order(self): - # midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox - # midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox - midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production - midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production + midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox + midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox + # midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production + # midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production so_number = self.name so_number = so_number.replace('/', '-') @@ -2000,8 +2000,8 @@ class SaleOrder(models.Model): } # ==== ENV ==== - # check_url = f'https://api.sandbox.midtrans.com/v2/{so_number}/status' # dev - sandbox - check_url = f'https://api.midtrans.com/v2/{so_number}/status' # production + check_url = f'https://api.sandbox.midtrans.com/v2/{so_number}/status' # dev - sandbox + # check_url = f'https://api.midtrans.com/v2/{so_number}/status' # production # ============================================= check_response = requests.get(check_url, headers=headers) -- cgit v1.2.3 From a9aff3725c86ae6e864e8b5e2b45596ef7dff6e0 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 8 Oct 2025 17:15:05 +0700 Subject: fix bug --- indoteknik_custom/models/partial_delivery.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/partial_delivery.py b/indoteknik_custom/models/partial_delivery.py index c9d2ba5c..c9b188ea 100644 --- a/indoteknik_custom/models/partial_delivery.py +++ b/indoteknik_custom/models/partial_delivery.py @@ -74,9 +74,6 @@ class PartialDeliveryWizard(models.TransientModel): if not selected_lines: raise UserError(_("Tidak ada produk yang dipilih atau diisi jumlahnya.")) - - if selected_lines.selected_qty > selected_lines.reserved_qty: - raise UserError(_("Jumlah produk yang dipilih melebihi jumlah reserved.")) new_picking = StockPicking.create({ 'origin': picking.origin, @@ -89,6 +86,8 @@ class PartialDeliveryWizard(models.TransientModel): }) for line in selected_lines: + if line.selected_qty > line.reserved_qty: + raise UserError(_("Jumlah produk %s yang dipilih melebihi jumlah reserved.") % line.product_id.display_name) move = line.move_id move._do_unreserve() -- cgit v1.2.3 From 393958c36bf49b7b2dbd888b81c345b7734151a0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 9 Oct 2025 11:35:17 +0700 Subject: balikin del dep date --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 8e947267..87182277 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1654,10 +1654,10 @@ class StockPicking(models.Model): line.sale_line_id = sale_line.id def write(self, vals): - allowed_user_ids = [17, 6277] - if 'driver_departure_date' in vals: - if self.env.user.id not in allowed_user_ids and not self.env.context.get('allow_driver_departure_edit'): - raise UserError("Hanya Denise dan Faisal yang dapat mengubah Delivery Departure Date.") + # allowed_user_ids = [17, 6277] + # if 'driver_departure_date' in vals: + # if self.env.user.id not in allowed_user_ids and not self.env.context.get('allow_driver_departure_edit'): + # raise UserError("Hanya Denise dan Faisal yang dapat mengubah Delivery Departure Date.") if 'linked_manual_bu_out' in vals: for record in self: -- cgit v1.2.3 From b25d39b4d5dd456ef2e40a25ce3608e5c9b6694d Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 9 Oct 2025 14:55:00 +0700 Subject: fix delivery departure date validation --- indoteknik_custom/models/stock_picking.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 87182277..8a5888fc 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -471,9 +471,9 @@ class StockPicking(models.Model): if len(picking.scan_koli_lines) > 0: if len(picking.scan_koli_lines) != picking.total_mapping_koli: raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli") - - picking.driver_departure_date = now - + picking.with_context(allow_driver_departure_edit=True).write({ + 'driver_departure_date': now + }) @api.depends('total_so_koli') def _compute_total_so_koli(self): for picking in self: @@ -1654,10 +1654,10 @@ class StockPicking(models.Model): line.sale_line_id = sale_line.id def write(self, vals): - # allowed_user_ids = [17, 6277] - # if 'driver_departure_date' in vals: - # if self.env.user.id not in allowed_user_ids and not self.env.context.get('allow_driver_departure_edit'): - # raise UserError("Hanya Denise dan Faisal yang dapat mengubah Delivery Departure Date.") + allowed_user_ids = [17, 6277] + if 'driver_departure_date' in vals: + if self.env.user.id not in allowed_user_ids and not self.env.context.get('allow_driver_departure_edit'): + raise UserError("Hanya Denise dan Faisal yang dapat mengubah Delivery Departure Date.") if 'linked_manual_bu_out' in vals: for record in self: -- cgit v1.2.3 From 137e812a5d96e1d5a82ceffbdf6a0938358e4d07 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 10 Oct 2025 09:50:49 +0700 Subject: add lampiran ke xx/xx to sj tele --- indoteknik_custom/models/sj_tele.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index 5ea08340..3ef4b877 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -66,10 +66,12 @@ class SjTele(models.Model): header = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n" BUB = 20 # jumlah baris per bubble + total = (len(lines) + BUB - 1) // BUB # total bubble for i in range(0, len(lines), BUB): body = "\n".join(lines[i:i + BUB]) - text = header + body + bagian = (i // BUB) + 1 + text = f"{header}Lampiran ke {bagian}/{total}\n{body}" try: r = requests.post( api_base + "/sendMessage", -- cgit v1.2.3 From fd3ce46f21aa78d9b9caa6ffdd1b9f61d89dfa65 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 10 Oct 2025 10:42:39 +0700 Subject: select all and unselect all --- indoteknik_custom/models/partial_delivery.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/partial_delivery.py b/indoteknik_custom/models/partial_delivery.py index c9b188ea..1204089b 100644 --- a/indoteknik_custom/models/partial_delivery.py +++ b/indoteknik_custom/models/partial_delivery.py @@ -41,6 +41,32 @@ class PartialDeliveryWizard(models.TransientModel): # res['line_ids'] = lines # return res + def action_select_all(self): + for line in self.line_ids: + line.selected = True + # return action supaya wizard gak nutup + return { + 'type': 'ir.actions.act_window', + 'res_model': self._name, + 'view_mode': 'form', + 'res_id': self.id, + 'target': 'new', + } + + def action_unselect_all(self): + for line in self.line_ids: + line.selected = False + # juga reload biar tetap di wizard + return { + 'type': 'ir.actions.act_window', + 'res_model': self._name, + 'view_mode': 'form', + 'res_id': self.id, + 'target': 'new', + } + + + @api.onchange('picking_id') def _onchange_picking_id(self): """Generate lines whenever picking_id is changed""" -- cgit v1.2.3 From e8ffbec31ade7e33d4da2c82c1c675f9291b38ef Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 10 Oct 2025 10:55:28 +0700 Subject: approval datedoc del dep date --- indoteknik_custom/models/approval_date_doc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/approval_date_doc.py b/indoteknik_custom/models/approval_date_doc.py index 638b44d7..3ba3b8dc 100644 --- a/indoteknik_custom/models/approval_date_doc.py +++ b/indoteknik_custom/models/approval_date_doc.py @@ -40,7 +40,7 @@ class ApprovalDateDoc(models.Model): raise UserError("Hanya Accounting Yang Bisa Approve") self.check_invoice_so_picking # Tambahkan context saat mengupdate date_doc_kirim - self.picking_id.with_context(from_button_approve=True).write({ + self.picking_id.with_context(from_button_approve=True, allow_driver_departure_edit=True).write({ 'driver_departure_date': self.driver_departure_date, 'date_doc_kirim': self.driver_departure_date, 'update_date_doc_kirim_add': True -- cgit v1.2.3 From e4b191155bf44bfcd58d6ae1b95d4a112bd43547 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 10 Oct 2025 11:17:41 +0700 Subject: balikin driver dep date --- indoteknik_custom/models/approval_date_doc.py | 2 +- indoteknik_custom/models/stock_picking.py | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/approval_date_doc.py b/indoteknik_custom/models/approval_date_doc.py index 3ba3b8dc..638b44d7 100644 --- a/indoteknik_custom/models/approval_date_doc.py +++ b/indoteknik_custom/models/approval_date_doc.py @@ -40,7 +40,7 @@ class ApprovalDateDoc(models.Model): raise UserError("Hanya Accounting Yang Bisa Approve") self.check_invoice_so_picking # Tambahkan context saat mengupdate date_doc_kirim - self.picking_id.with_context(from_button_approve=True, allow_driver_departure_edit=True).write({ + self.picking_id.with_context(from_button_approve=True).write({ 'driver_departure_date': self.driver_departure_date, 'date_doc_kirim': self.driver_departure_date, 'update_date_doc_kirim_add': True diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 8a5888fc..4772c433 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -471,9 +471,9 @@ class StockPicking(models.Model): if len(picking.scan_koli_lines) > 0: if len(picking.scan_koli_lines) != picking.total_mapping_koli: raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli") - picking.with_context(allow_driver_departure_edit=True).write({ - 'driver_departure_date': now - }) + + picking.driver_departure_date = now + @api.depends('total_so_koli') def _compute_total_so_koli(self): for picking in self: @@ -1654,11 +1654,6 @@ class StockPicking(models.Model): line.sale_line_id = sale_line.id def write(self, vals): - allowed_user_ids = [17, 6277] - if 'driver_departure_date' in vals: - if self.env.user.id not in allowed_user_ids and not self.env.context.get('allow_driver_departure_edit'): - raise UserError("Hanya Denise dan Faisal yang dapat mengubah Delivery Departure Date.") - if 'linked_manual_bu_out' in vals: for record in self: if (record.picking_type_code == 'internal' -- cgit v1.2.3 From b0d0e26965ab1ba7aa0c5a607cc0a12a67546dfc Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Fri, 10 Oct 2025 14:48:36 +0700 Subject: (andri) fix --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index 8ed92fc8..6e8498f7 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -365,7 +365,7 @@ class UserPengajuanTempoRequest(models.Model): @api.onchange('tempo_duration') def _tempo_duration_change(self): for tempo in self: - if tempo.env.user.id not in (7, 688, 28, 377, 12182, 375): + if tempo.env.user.id not in (7, 688, 28, 377, 12182, 375, 19): raise UserError("Durasi tempo hanya bisa di ubah oleh Sales Manager atau Direktur") @api.onchange('tempo_limit') -- cgit v1.2.3 From 23a2d929b209c6121d3bf4e3b35e6bfec4a99e8d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 13 Oct 2025 11:09:37 +0700 Subject: fix bug --- indoteknik_custom/models/partial_delivery.py | 85 +++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 13 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/partial_delivery.py b/indoteknik_custom/models/partial_delivery.py index 1204089b..83fe9981 100644 --- a/indoteknik_custom/models/partial_delivery.py +++ b/indoteknik_custom/models/partial_delivery.py @@ -42,6 +42,8 @@ class PartialDeliveryWizard(models.TransientModel): # return res def action_select_all(self): + if len(self.line_ids) == 0: + raise UserError(_("Tidak ada produk yang dipilih.")) for line in self.line_ids: line.selected = True # return action supaya wizard gak nutup @@ -54,6 +56,8 @@ class PartialDeliveryWizard(models.TransientModel): } def action_unselect_all(self): + if len(self.line_ids) == 0: + raise UserError(_("Tidak ada produk yang dipilih.")) for line in self.line_ids: line.selected = False # juga reload biar tetap di wizard @@ -65,21 +69,41 @@ class PartialDeliveryWizard(models.TransientModel): 'target': 'new', } - - @api.onchange('picking_id') def _onchange_picking_id(self): """Generate lines whenever picking_id is changed""" - lines = [] - if self.picking_id: - moves = self.picking_id.move_ids_without_package.filtered(lambda m: m.reserved_availability > 0) - for move in moves: - lines.append((0, 0, { - 'product_id': move.product_id.id, - 'reserved_qty': move.reserved_availability, - 'move_id': move.id, - })) - self.line_ids = lines + if not self.picking_id: + self.line_ids = [(5, 0, 0)] + return + + # ๐Ÿงน hapus line lama dulu + if self.line_ids: + self.line_ids.unlink() + + moves = self.picking_id.move_lines or self.picking_id.move_ids_without_package + moves = moves.filtered(lambda m: m.product_id and m.reserved_availability > 0) + + if not moves: + _logger.warning(f"[PartialDeliveryWizard] Tidak ada move line di picking {self.picking_id.name}") + return + + for move in moves: + reserved_qty = move.reserved_availability or 0.0 + ordered_qty = move.sale_line_id.product_uom_qty if move.sale_line_id else 0.0 + + self.env['partial.delivery.wizard.line'].create({ + 'wizard_id': self.id, + 'product_id': move.product_id.id, + 'reserved_qty': reserved_qty, + 'selected_qty': reserved_qty, # biar langsung keisi default + 'move_id': move.id, + 'sale_line_id': move.sale_line_id.id if move.sale_line_id else False, + }) + + _logger.info( + f"[PartialDeliveryWizard] โœ… Created line for {move.product_id.display_name} " + f"(reserved={reserved_qty}, move_id={move.id})" + ) def action_confirm_partial_delivery(self): @@ -92,7 +116,6 @@ class PartialDeliveryWizard(models.TransientModel): if picking.state != "assigned": raise UserError(_("Picking harus dalam status Ready (assigned).")) - lines_by_qty = self.line_ids.filtered(lambda l: l.selected_qty > 0) lines_by_selected = self.line_ids.filtered(lambda l: l.selected and not l.selected_qty) @@ -101,6 +124,37 @@ class PartialDeliveryWizard(models.TransientModel): if not selected_lines: raise UserError(_("Tidak ada produk yang dipilih atau diisi jumlahnya.")) + # ๐Ÿง  Tambahan: kalau semua line dipilih (full delivery) + all_selected = len(selected_lines) == len(self.line_ids) + full_selected = all_selected and all( + (line.selected_qty or line.reserved_qty) >= line.reserved_qty + for line in selected_lines + ) + + if full_selected: + # ๐Ÿ’ก Gak perlu bikin picking baru, langsung ubah state_reserve + picking.write({'state_reserve': 'partial'}) + + # Tambahin log aja biar ada jejak + picking.message_post( + body=f"Full Picking Confirmed dari wizard partial delivery oleh {self.env.user.name}", + message_type="comment", + subtype_xmlid="mail.mt_note", + ) + + return { + "type": "ir.actions.act_window", + "res_model": "stock.picking", + "view_mode": "form", + "res_id": picking.id, + "target": "current", + "effect": { + "fadeout": "slow", + "message": f"โœ… Semua produk dikirim penuh โ€” tidak dibuat DO baru.", + "type": "rainbow_man", + }, + } + new_picking = StockPicking.create({ 'origin': picking.origin, 'partner_id': picking.partner_id.id, @@ -212,3 +266,8 @@ class PartialDeliveryWizardLine(models.TransientModel): sale_line_id = fields.Many2one('sale.order.line', string="SO Line", related='move_id.sale_line_id') ordered_qty = fields.Float(related='sale_line_id.product_uom_qty', string="Ordered Qty") + @api.onchange('selected') + def onchange_selected(self): + if self.selected: + self.selected_qty = self.reserved_qty + -- cgit v1.2.3 From 40c66acf47f900ecd776358758ac053347c078c7 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 13 Oct 2025 11:21:04 +0700 Subject: set back to pengajuan1 when CO from website and change SO line behavior onchange vendor_id --- indoteknik_custom/models/sale_order.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4a7203a1..2d2d71b3 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -3230,6 +3230,7 @@ class SaleOrder(models.Model): # order._auto_set_shipping_from_website() order._compute_etrts_date() order._validate_expected_ready_ship_date() + order.onchange_vendor_id() # order._validate_delivery_amt() # order._check_total_margin_excl_third_party() # order._update_partner_details() -- cgit v1.2.3 From dab2cdebb698b817b979efd2087175060f9b8f03 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 13 Oct 2025 13:01:35 +0700 Subject: change SO line behavior onchange vendor_id --- indoteknik_custom/models/sale_order.py | 11 ++++- indoteknik_custom/models/sale_order_line.py | 62 ++++++++++++++++++----------- 2 files changed, 49 insertions(+), 24 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 2d2d71b3..3bd1ca59 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -3230,7 +3230,10 @@ class SaleOrder(models.Model): # order._auto_set_shipping_from_website() order._compute_etrts_date() order._validate_expected_ready_ship_date() - order.onchange_vendor_id() + for line in order.order_line: + updated_vals = line._update_purchase_info() + if updated_vals: + line.write(updated_vals) # order._validate_delivery_amt() # order._check_total_margin_excl_third_party() # order._update_partner_details() @@ -3362,6 +3365,12 @@ class SaleOrder(models.Model): if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() + for order in self: + for line in order.order_line: + updated_vals = line._update_purchase_info() + if updated_vals: + line.write(updated_vals) + return res def button_refund(self): diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 1f2ea1fb..f8fb898c 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -247,29 +247,29 @@ class SaleOrderLine(models.Model): margin_per_item = sales_price - purchase_price line.item_before_margin = margin_per_item - @api.onchange('vendor_id') - def onchange_vendor_id(self): - # TODO : need to change this logic @stephan - if not self.product_id or self.product_id.type == 'service': - return - elif self.product_id.categ_id.id == 34: # finish good / manufacturing only - cost = self.product_id.standard_price - self.purchase_price = cost - elif self.product_id.x_manufacture.override_vendor_id: - # purchase_price = self.env['purchase.pricelist'].search( - # [('vendor_id', '=', self.product_id.x_manufacture.override_vendor_id.id), - # ('product_id', '=', self.product_id.id)], - # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') - price, taxes, vendor_id = self._get_purchase_price_by_vendor(self.product_id, self.vendor_id) - self.purchase_price = price - self.purchase_tax_id = taxes - # else: - # purchase_price = self.env['purchase.pricelist'].search( - # [('vendor_id', '=', self.vendor_id.id), ('product_id', '=', self.product_id.id)], - # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') - # price, taxes = self._get_valid_purchase_price(purchase_price) - # self.purchase_price = price - # self.purchase_tax_id = taxes + # @api.onchange('vendor_id') + # def onchange_vendor_id(self): + # # TODO : need to change this logic @stephan + # if not self.product_id or self.product_id.type == 'service': + # return + # elif self.product_id.categ_id.id == 34: # finish good / manufacturing only + # cost = self.product_id.standard_price + # self.purchase_price = cost + # elif self.product_id.x_manufacture.override_vendor_id: + # # purchase_price = self.env['purchase.pricelist'].search( + # # [('vendor_id', '=', self.product_id.x_manufacture.override_vendor_id.id), + # # ('product_id', '=', self.product_id.id)], + # # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') + # price, taxes, vendor_id = self._get_purchase_price_by_vendor(self.product_id, self.vendor_id) + # self.purchase_price = price + # self.purchase_tax_id = taxes + # # else: + # # purchase_price = self.env['purchase.pricelist'].search( + # # [('vendor_id', '=', self.vendor_id.id), ('product_id', '=', self.product_id.id)], + # # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') + # # price, taxes = self._get_valid_purchase_price(purchase_price) + # # self.purchase_price = price + # # self.purchase_tax_id = taxes # def _calculate_selling_price(self): # rec_purchase_price, rec_taxes, rec_vendor_id = self._get_purchase_price(self.product_id) @@ -512,3 +512,19 @@ class SaleOrderLine(models.Model): else: line.product_updatable = False # line.desc_updatable = False + + @api.onchange('vendor_id') + def _onchange_vendor_id_custom(self): + self._update_purchase_info() + + def _update_purchase_info(self): + if not self.product_id or self.product_id.type == 'service': + return + + if self.product_id.categ_id.id == 34: + self.purchase_price = self.product_id.standard_price + self.purchase_tax_id = [(5, 0, 0)] # reset + elif self.product_id.x_manufacture.override_vendor_id: + price, taxes, vendor_id = self._get_purchase_price_by_vendor(self.product_id, self.vendor_id) + self.purchase_price = price + self.purchase_tax_id = taxes -- cgit v1.2.3 From 724d7ed6d85ecc3acadbcf56a98aead0512af01d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 14 Oct 2025 14:11:18 +0700 Subject: push --- indoteknik_custom/models/partial_delivery.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/partial_delivery.py b/indoteknik_custom/models/partial_delivery.py index 83fe9981..977cceed 100644 --- a/indoteknik_custom/models/partial_delivery.py +++ b/indoteknik_custom/models/partial_delivery.py @@ -46,7 +46,6 @@ class PartialDeliveryWizard(models.TransientModel): raise UserError(_("Tidak ada produk yang dipilih.")) for line in self.line_ids: line.selected = True - # return action supaya wizard gak nutup return { 'type': 'ir.actions.act_window', 'res_model': self._name, @@ -60,7 +59,6 @@ class PartialDeliveryWizard(models.TransientModel): raise UserError(_("Tidak ada produk yang dipilih.")) for line in self.line_ids: line.selected = False - # juga reload biar tetap di wizard return { 'type': 'ir.actions.act_window', 'res_model': self._name, @@ -76,7 +74,6 @@ class PartialDeliveryWizard(models.TransientModel): self.line_ids = [(5, 0, 0)] return - # ๐Ÿงน hapus line lama dulu if self.line_ids: self.line_ids.unlink() @@ -95,7 +92,7 @@ class PartialDeliveryWizard(models.TransientModel): 'wizard_id': self.id, 'product_id': move.product_id.id, 'reserved_qty': reserved_qty, - 'selected_qty': reserved_qty, # biar langsung keisi default + # 'selected_qty': reserved_qty, 'move_id': move.id, 'sale_line_id': move.sale_line_id.id if move.sale_line_id else False, }) @@ -119,12 +116,11 @@ class PartialDeliveryWizard(models.TransientModel): lines_by_qty = self.line_ids.filtered(lambda l: l.selected_qty > 0) lines_by_selected = self.line_ids.filtered(lambda l: l.selected and not l.selected_qty) - selected_lines = lines_by_qty | lines_by_selected # gabung dua domain hasil filter + selected_lines = lines_by_qty | lines_by_selected if not selected_lines: raise UserError(_("Tidak ada produk yang dipilih atau diisi jumlahnya.")) - # ๐Ÿง  Tambahan: kalau semua line dipilih (full delivery) all_selected = len(selected_lines) == len(self.line_ids) full_selected = all_selected and all( (line.selected_qty or line.reserved_qty) >= line.reserved_qty @@ -132,10 +128,8 @@ class PartialDeliveryWizard(models.TransientModel): ) if full_selected: - # ๐Ÿ’ก Gak perlu bikin picking baru, langsung ubah state_reserve picking.write({'state_reserve': 'partial'}) - # Tambahin log aja biar ada jejak picking.message_post( body=f"Full Picking Confirmed dari wizard partial delivery oleh {self.env.user.name}", message_type="comment", @@ -171,11 +165,9 @@ class PartialDeliveryWizard(models.TransientModel): move = line.move_id move._do_unreserve() - # kalau cuma selected tanpa isi qty, otomatis set selected_qty = reserved_qty if line.selected and not line.selected_qty: line.selected_qty = line.reserved_qty - # MODE 1 โ†’ Prioritas kalau ada selected_qty if line.selected_qty > 0: if line.selected_qty > move.product_uom_qty: raise UserError(_( @@ -184,7 +176,6 @@ class PartialDeliveryWizard(models.TransientModel): if line.selected_qty < move.product_uom_qty: qty_to_keep = move.product_uom_qty - line.selected_qty - # split move new_move = move.copy(default={ 'product_uom_qty': line.selected_qty, 'picking_id': new_picking.id, @@ -192,19 +183,13 @@ class PartialDeliveryWizard(models.TransientModel): }) move.write({'product_uom_qty': qty_to_keep}) else: - # full pindah move.write({'picking_id': new_picking.id, 'partial': True}) - - - # Confirm & assign DO baru new_picking.action_confirm() new_picking.action_assign() - # Reassign DO lama biar sisa qty ke-update picking.action_assign() - # --- ๐Ÿ”ข Rename picking baru dengan format "/(Nomor urut)" --- existing_partials = self.env['stock.picking'].search([ ('origin', '=', picking.origin), ('state_reserve', '=', 'partial'), @@ -220,7 +205,6 @@ class PartialDeliveryWizard(models.TransientModel): new_name = f"{picking.name}/{suffix_number}" new_picking.name = new_name - # --- ๐Ÿ’ฌ Post message ke SO --- if picking.origin: sale_order = self.env['sale.order'].search([('name', '=', picking.origin)], limit=1) if sale_order: @@ -231,7 +215,6 @@ class PartialDeliveryWizard(models.TransientModel): subtype_xmlid="mail.mt_note", ) - # --- ๐Ÿ“ Log di DO baru --- new_picking.message_post( body=f"Partial Picking created dari {picking.name} oleh {self.env.user.name}", message_type="comment", -- cgit v1.2.3 From b41c2d160e5e114bf805baed573d791fbac3feac Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 14 Oct 2025 14:12:23 +0700 Subject: push --- indoteknik_custom/models/partial_delivery.py | 37 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/partial_delivery.py b/indoteknik_custom/models/partial_delivery.py index 977cceed..4df7da1e 100644 --- a/indoteknik_custom/models/partial_delivery.py +++ b/indoteknik_custom/models/partial_delivery.py @@ -116,22 +116,31 @@ class PartialDeliveryWizard(models.TransientModel): lines_by_qty = self.line_ids.filtered(lambda l: l.selected_qty > 0) lines_by_selected = self.line_ids.filtered(lambda l: l.selected and not l.selected_qty) - selected_lines = lines_by_qty | lines_by_selected + selected_lines = lines_by_qty | lines_by_selected # gabung dua domain hasil filter if not selected_lines: raise UserError(_("Tidak ada produk yang dipilih atau diisi jumlahnya.")) - all_selected = len(selected_lines) == len(self.line_ids) - full_selected = all_selected and all( - (line.selected_qty or line.reserved_qty) >= line.reserved_qty - for line in selected_lines + # ๐Ÿง  Cek apakah semua move di DO sudah muncul di wizard dan semua dipilih + picking_move_ids = picking.move_ids_without_package.ids + wizard_move_ids = self.line_ids.mapped('move_id').ids + + # Semua move DO muncul di wizard, dan semua baris dipilih + full_selected = ( + set(picking_move_ids) == set(wizard_move_ids) + and len(selected_lines) == len(self.line_ids) + and all( + (line.selected_qty or line.reserved_qty) >= line.reserved_qty + for line in selected_lines + ) ) if full_selected: + # ๐Ÿ’ก Gak perlu bikin picking baru, langsung ubah state_reserve picking.write({'state_reserve': 'partial'}) picking.message_post( - body=f"Full Picking Confirmed dari wizard partial delivery oleh {self.env.user.name}", + body=f"Full Picking Confirmed via wizard partial delivery oleh {self.env.user.name} (tanpa DO baru)", message_type="comment", subtype_xmlid="mail.mt_note", ) @@ -144,11 +153,12 @@ class PartialDeliveryWizard(models.TransientModel): "target": "current", "effect": { "fadeout": "slow", - "message": f"โœ… Semua produk dikirim penuh โ€” tidak dibuat DO baru.", + "message": f"โœ… Semua produk dari DO ini dikirim penuh โ€” tidak dibuat DO baru.", "type": "rainbow_man", }, } + # ๐Ÿงฉ Kalau bukan full selected, lanjut bikin DO baru new_picking = StockPicking.create({ 'origin': picking.origin, 'partner_id': picking.partner_id.id, @@ -187,7 +197,6 @@ class PartialDeliveryWizard(models.TransientModel): new_picking.action_confirm() new_picking.action_assign() - picking.action_assign() existing_partials = self.env['stock.picking'].search([ @@ -196,14 +205,8 @@ class PartialDeliveryWizard(models.TransientModel): ('id', '!=', new_picking.id), ], order='name asc') - suffix_number = len(existing_partials) - if suffix_number == 0: - suffix_number = 1 - else: - suffix_number += 1 - - new_name = f"{picking.name}/{suffix_number}" - new_picking.name = new_name + suffix_number = len(existing_partials) + 1 + new_picking.name = f"{picking.name}/{suffix_number}" if picking.origin: sale_order = self.env['sale.order'].search([('name', '=', picking.origin)], limit=1) @@ -234,8 +237,6 @@ class PartialDeliveryWizard(models.TransientModel): }, } - - class PartialDeliveryWizardLine(models.TransientModel): _name = 'partial.delivery.wizard.line' _description = 'Partial Delivery Wizard Line' -- cgit v1.2.3 From 4502ebb5452b150b5e36175bb7900bace760e9f4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 15 Oct 2025 09:38:46 +0700 Subject: push fix bug --- indoteknik_custom/models/sale_order_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index f8fb898c..12e7e6f9 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -523,7 +523,7 @@ class SaleOrderLine(models.Model): if self.product_id.categ_id.id == 34: self.purchase_price = self.product_id.standard_price - self.purchase_tax_id = [(5, 0, 0)] # reset + self.purchase_tax_id = False # reset elif self.product_id.x_manufacture.override_vendor_id: price, taxes, vendor_id = self._get_purchase_price_by_vendor(self.product_id, self.vendor_id) self.purchase_price = price -- cgit v1.2.3 From b6bf4c8b58b9311c6a6cf7f745cb42b025159759 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 15 Oct 2025 12:08:34 +0700 Subject: SO line onchange vendor id --- indoteknik_custom/models/sale_order.py | 18 +++++------ indoteknik_custom/models/sale_order_line.py | 46 ++++++++++++++--------------- 2 files changed, 32 insertions(+), 32 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 3bd1ca59..a5e2f7c4 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -3230,10 +3230,10 @@ class SaleOrder(models.Model): # order._auto_set_shipping_from_website() order._compute_etrts_date() order._validate_expected_ready_ship_date() - for line in order.order_line: - updated_vals = line._update_purchase_info() - if updated_vals: - line.write(updated_vals) + # for line in order.order_line: + # updated_vals = line._update_purchase_info() + # if updated_vals: + # line.write(updated_vals) # order._validate_delivery_amt() # order._check_total_margin_excl_third_party() # order._update_partner_details() @@ -3365,11 +3365,11 @@ class SaleOrder(models.Model): if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() - for order in self: - for line in order.order_line: - updated_vals = line._update_purchase_info() - if updated_vals: - line.write(updated_vals) + # for order in self: + # for line in order.order_line: + # updated_vals = line._update_purchase_info() + # if updated_vals: + # line.write(updated_vals) return res diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 12e7e6f9..bd2600ae 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -247,29 +247,29 @@ class SaleOrderLine(models.Model): margin_per_item = sales_price - purchase_price line.item_before_margin = margin_per_item - # @api.onchange('vendor_id') - # def onchange_vendor_id(self): - # # TODO : need to change this logic @stephan - # if not self.product_id or self.product_id.type == 'service': - # return - # elif self.product_id.categ_id.id == 34: # finish good / manufacturing only - # cost = self.product_id.standard_price - # self.purchase_price = cost - # elif self.product_id.x_manufacture.override_vendor_id: - # # purchase_price = self.env['purchase.pricelist'].search( - # # [('vendor_id', '=', self.product_id.x_manufacture.override_vendor_id.id), - # # ('product_id', '=', self.product_id.id)], - # # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') - # price, taxes, vendor_id = self._get_purchase_price_by_vendor(self.product_id, self.vendor_id) - # self.purchase_price = price - # self.purchase_tax_id = taxes - # # else: - # # purchase_price = self.env['purchase.pricelist'].search( - # # [('vendor_id', '=', self.vendor_id.id), ('product_id', '=', self.product_id.id)], - # # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') - # # price, taxes = self._get_valid_purchase_price(purchase_price) - # # self.purchase_price = price - # # self.purchase_tax_id = taxes + @api.onchange('vendor_id') + def onchange_vendor_id(self): + # TODO : need to change this logic @stephan + if not self.product_id or self.product_id.type == 'service': + return + elif self.product_id.categ_id.id == 34: # finish good / manufacturing only + cost = self.product_id.standard_price + self.purchase_price = cost + elif self.product_id.x_manufacture.override_vendor_id: + # purchase_price = self.env['purchase.pricelist'].search( + # [('vendor_id', '=', self.product_id.x_manufacture.override_vendor_id.id), + # ('product_id', '=', self.product_id.id)], + # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') + price, taxes, vendor_id = self._get_purchase_price_by_vendor(self.product_id, self.vendor_id) + self.purchase_price = price + self.purchase_tax_id = taxes + # else: + # purchase_price = self.env['purchase.pricelist'].search( + # [('vendor_id', '=', self.vendor_id.id), ('product_id', '=', self.product_id.id)], + # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') + # price, taxes = self._get_valid_purchase_price(purchase_price) + # self.purchase_price = price + # self.purchase_tax_id = taxes # def _calculate_selling_price(self): # rec_purchase_price, rec_taxes, rec_vendor_id = self._get_purchase_price(self.product_id) -- cgit v1.2.3 From ee6d5d4433a397ca5cdf3474f29bcbd0b2553736 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 15 Oct 2025 12:18:34 +0700 Subject: SO line onchange vendor id --- indoteknik_custom/models/sale_order_line.py | 48 ++++++++++++++--------------- 1 file changed, 24 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 bd2600ae..1df1a058 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -247,29 +247,29 @@ class SaleOrderLine(models.Model): margin_per_item = sales_price - purchase_price line.item_before_margin = margin_per_item - @api.onchange('vendor_id') - def onchange_vendor_id(self): - # TODO : need to change this logic @stephan - if not self.product_id or self.product_id.type == 'service': - return - elif self.product_id.categ_id.id == 34: # finish good / manufacturing only - cost = self.product_id.standard_price - self.purchase_price = cost - elif self.product_id.x_manufacture.override_vendor_id: - # purchase_price = self.env['purchase.pricelist'].search( - # [('vendor_id', '=', self.product_id.x_manufacture.override_vendor_id.id), - # ('product_id', '=', self.product_id.id)], - # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') - price, taxes, vendor_id = self._get_purchase_price_by_vendor(self.product_id, self.vendor_id) - self.purchase_price = price - self.purchase_tax_id = taxes - # else: - # purchase_price = self.env['purchase.pricelist'].search( - # [('vendor_id', '=', self.vendor_id.id), ('product_id', '=', self.product_id.id)], - # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') - # price, taxes = self._get_valid_purchase_price(purchase_price) - # self.purchase_price = price - # self.purchase_tax_id = taxes + # @api.onchange('vendor_id') + # def onchange_vendor_id(self): + # # TODO : need to change this logic @stephan + # if not self.product_id or self.product_id.type == 'service': + # return + # elif self.product_id.categ_id.id == 34: # finish good / manufacturing only + # cost = self.product_id.standard_price + # self.purchase_price = cost + # elif self.product_id.x_manufacture.override_vendor_id: + # # purchase_price = self.env['purchase.pricelist'].search( + # # [('vendor_id', '=', self.product_id.x_manufacture.override_vendor_id.id), + # # ('product_id', '=', self.product_id.id)], + # # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') + # price, taxes, vendor_id = self._get_purchase_price_by_vendor(self.product_id, self.vendor_id) + # self.purchase_price = price + # self.purchase_tax_id = taxes + # # else: + # # purchase_price = self.env['purchase.pricelist'].search( + # # [('vendor_id', '=', self.vendor_id.id), ('product_id', '=', self.product_id.id)], + # # limit=1, order='count_trx_po desc, count_trx_po_vendor desc') + # # price, taxes = self._get_valid_purchase_price(purchase_price) + # # self.purchase_price = price + # # self.purchase_tax_id = taxes # def _calculate_selling_price(self): # rec_purchase_price, rec_taxes, rec_vendor_id = self._get_purchase_price(self.product_id) @@ -523,7 +523,7 @@ class SaleOrderLine(models.Model): if self.product_id.categ_id.id == 34: self.purchase_price = self.product_id.standard_price - self.purchase_tax_id = False # reset + self.purchase_tax_id = False elif self.product_id.x_manufacture.override_vendor_id: price, taxes, vendor_id = self._get_purchase_price_by_vendor(self.product_id, self.vendor_id) self.purchase_price = price -- cgit v1.2.3