From eb8a67a33b4b8abd574f737bc5727ffdb5b18b99 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 1 Oct 2025 17:12:48 +0700 Subject: remove email mapping --- indoteknik_api/controllers/api_v1/stock_picking.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index 7f878ad2..deb92a50 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -129,20 +129,6 @@ class StockPicking(controller.Controller): paket_document = kw.get('paket_document', False) dispatch_document = kw.get('dispatch_document', False) - # ===== Role by EMAIL ===== - driver_emails = { - 'driverindoteknik@gmail.com', - 'sulistianaridwan8@gmail.com', - } - dispatch_emails = { - 'rahmat.afiudin@gmail.com', - 'indraprtama60@gmail.com' - } - - login = (request.env.user.login or '').lower() - is_dispatch_user = login in dispatch_emails - is_driver_user = (login in driver_emails) and not is_dispatch_user - # if not sj_document or not paket_document: # return self.response(code=400, description='dispatch_document wajib untuk role dispatch login= %s' % login) -- cgit v1.2.3 From 50d5643a2c4a7b245b37f8ca0dd4fc5383e61d4e Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 2 Oct 2025 08:22:30 +0700 Subject: vals SP doc --- indoteknik_api/controllers/api_v1/stock_picking.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index deb92a50..2ec1ec2a 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -125,15 +125,9 @@ class StockPicking(controller.Controller): @http.route(prefix + 'stock-picking//documentation', auth='public', methods=['PUT', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized() def write_partner_stock_picking_documentation(self, scanid, **kw): - sj_document = kw.get('sj_document', False) - paket_document = kw.get('paket_document', False) - dispatch_document = kw.get('dispatch_document', False) - - # if not sj_document or not paket_document: - # return self.response(code=400, description='dispatch_document wajib untuk role dispatch login= %s' % login) - - # if is_dispatch_user and not dispatch_document and not is_driver_user: - # return self.response(code=400, description='dispatch_document wajib untuk role dispatch login= %s' % login) + sj_document = kw.get('sj_document') if 'sj_document' in kw else None + paket_document = kw.get('paket_document') if 'paket_document' in kw else None + dispatch_document = kw.get('dispatch_document') if 'dispatch_document' in kw else None # ===== Cari picking by id / picking_code ===== picking_data = False @@ -147,10 +141,12 @@ class StockPicking(controller.Controller): return self.response(code=403, description='picking not found') params = { - 'sj_documentation': sj_document, - 'paket_documentation': paket_document, 'driver_arrival_date': datetime.utcnow(), } + if sj_document: + params['sj_documentation'] = sj_document + if paket_document: + params['paket_documentation'] = paket_document if dispatch_document: params['dispatch_documentation'] = dispatch_document -- cgit v1.2.3 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(-) 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(+) 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(-) 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_api/controllers/api_v1/sale_order.py | 10 +++- indoteknik_custom/models/sale_order.py | 65 ++++++++++++++++++++----- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index 1a75c830..25268856 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -808,6 +808,14 @@ class SaleOrder(controller.Controller): sale_order.apply_promotion_program() sale_order.add_free_product(promotions) + # Pastikan baris hasil promo/bonus ditandai supaya bisa di-skip voucher + promo_lines = sale_order.order_line.filtered( + lambda l: getattr(l, 'order_promotion_id', False) or (l.price_unit or 0.0) == 0.0 + ) + if promo_lines: + promo_lines.write({'is_has_disc': True}) + _logger.info(f"[PROMO_MARK] Marked {len(promo_lines)} promo/free lines as is_has_disc=True") + voucher_code = params['value']['voucher'] if voucher_code: _logger.info(f"Processing voucher: {voucher_code}") @@ -816,7 +824,7 @@ class SaleOrder(controller.Controller): voucher_shipping = request.env['voucher'].search( [('code', '=', voucher_code), ('apply_type', 'in', ['shipping'])], limit=1) - if voucher and len(promotions) == 0: + if voucher: _logger.info("Applying regular voucher") sale_order.voucher_id = voucher.id sale_order.apply_voucher() 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(-) 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 +++++++++++++++++++++++-------- indoteknik_custom/views/stock_picking.xml | 10 +++++++ 2 files changed, 44 insertions(+), 12 deletions(-) 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: diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 21762202..7f0865f4 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -45,6 +45,14 @@ stock.picking + + +