diff options
| author | FIN-IT_AndriFP <it@fixcomart.co.id> | 2025-10-13 08:56:20 +0700 |
|---|---|---|
| committer | FIN-IT_AndriFP <it@fixcomart.co.id> | 2025-10-13 08:56:20 +0700 |
| commit | a9eac2968018d74c190ac376ce2b874e32ded3b6 (patch) | |
| tree | 0c675d880a0a7d7ea873e328f4eb20955cb10342 | |
| parent | 812bdbebbbe1cc0b1cc6294491f7771a668a04bd (diff) | |
| parent | 10ddd8835a98bbfe58abedf6a405929dfbbb76d0 (diff) | |
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into pum-v3
| -rw-r--r-- | indoteknik_api/controllers/api_v1/sale_order.py | 6 | ||||
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 1 | ||||
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/automatic_purchase.py | 161 | ||||
| -rw-r--r-- | indoteknik_custom/models/domain_apo.py | 12 | ||||
| -rw-r--r-- | indoteknik_custom/models/partial_delivery.py | 214 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 25 | ||||
| -rw-r--r-- | indoteknik_custom/models/sj_tele.py | 4 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_move.py | 1 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_picking.py | 8 | ||||
| -rw-r--r-- | indoteknik_custom/models/user_pengajuan_tempo_request.py | 2 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 3 | ||||
| -rw-r--r-- | indoteknik_custom/views/domain_apo.xml | 46 | ||||
| -rwxr-xr-x | indoteknik_custom/views/sale_order.xml | 54 | ||||
| -rw-r--r-- | indoteknik_custom/views/stock_picking.xml | 1 |
15 files changed, 436 insertions, 104 deletions
diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index 1c87400f..56b49f9a 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -231,11 +231,10 @@ class SaleOrder(controller.Controller): for so in filtered_orders_paginated: item = request.env['sale.order'].api_v1_single_response(so) - approval_ok = (so.approval_status in ('pengajuan1', 'pengajuan2')) source_ok = _is_website_order(so) term_ok = bool(so.payment_term_id and so.payment_term_id.id == CBD_PAYMENT_TERM_ID) pay_status = (getattr(so, 'payment_status', '') or '').strip().lower() - eligible = bool(approval_ok and source_ok and term_ok and pay_status in ALLOWED_CONTINUE) + eligible = bool(source_ok and term_ok and pay_status in ALLOWED_CONTINUE) redirect_url = getattr(so, 'payment_link_midtrans', '') or '' @@ -243,7 +242,6 @@ class SaleOrder(controller.Controller): 'eligibleContinue': eligible, 'paymentSummary': { 'eligible': eligible, - 'approvalStatus': so.approval_status or '', 'paymentStatus': pay_status, 'paymentTermId': so.payment_term_id.id if so.payment_term_id else None, 'sourceId': so.source_id.id if so.source_id else None, @@ -285,7 +283,6 @@ class SaleOrder(controller.Controller): pay_status = (getattr(sale_order, 'payment_status', '') or '').strip().lower() eligible = ( - sale_order.approval_status in ('pengajuan1', 'pengajuan2') and _is_website_order(sale_order) and sale_order.payment_term_id and sale_order.payment_term_id.id == CBD_PAYMENT_TERM_ID and pay_status in ALLOWED_CONTINUE @@ -317,7 +314,6 @@ class SaleOrder(controller.Controller): 'eligible_continue': eligible, 'payment_summary': { 'eligible': eligible, - 'approval_status': sale_order.approval_status or '', 'payment_status': pay_status, 'payment_term_id': sale_order.payment_term_id.id if sale_order.payment_term_id else None, 'source_id': sale_order.source_id.id if sale_order.source_id else None, diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index d064ece9..fe770b4c 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -189,6 +189,7 @@ # 'views/reimburse.xml', 'views/sj_tele.xml', 'views/close_tempo_mail_template.xml', + 'views/domain_apo.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 71e5b4b3..165dae4e 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -161,3 +161,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..1204089b --- /dev/null +++ b/indoteknik_custom/models/partial_delivery.py @@ -0,0 +1,214 @@ +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 + + 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""" + 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.")) + + 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: + 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() + + # 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"<b>Partial Delivery Created:</b> <a href=# data-oe-model='stock.picking' data-oe-id='{new_picking.id}'>{new_picking.name}</a> " + f"oleh {self.env.user.name}", + message_type="comment", + subtype_xmlid="mail.mt_note", + ) + + # --- 📝 Log di DO baru --- + new_picking.message_post( + body=f"<b>Partial Picking created</b> 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..4a7203a1 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'): @@ -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) @@ -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/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", 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): 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: 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') diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 7ca1f38c..893d3110 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -185,6 +185,9 @@ access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_match access_approval_payment_term,access.approval.payment.term,model_approval_payment_term,,1,1,1,1 access_purchase_order_update_date_wizard,access.purchase.order.update.date.wizard,model_purchase_order_update_date_wizard,,1,1,1,1 access_change_date_planned_wizard,access.change.date.planned.wizard,model_change_date_planned_wizard,,1,1,1,1 +access_partial_delivery_wizard,access.partial.delivery.wizard,model_partial_delivery_wizard,,1,1,1,1 +access_partial_delivery_wizard_line,access.partial.delivery.wizard.line,model_partial_delivery_wizard_line,,1,1,1,1 +access_apo_domain_config,access.apo.domain.config,model_apo_domain_config,base.group_user,1,1,1,1 access_refund_sale_order,access.refund.sale.order,model_refund_sale_order,base.group_user,1,1,1,1 access_refund_sale_order_line,access.refund.sale.order.line,model_refund_sale_order_line,base.group_user,1,1,1,1 access_advance_payment_request,access.advance.payment.request,model_advance_payment_request,,1,1,1,1 diff --git a/indoteknik_custom/views/domain_apo.xml b/indoteknik_custom/views/domain_apo.xml new file mode 100644 index 00000000..1dae473d --- /dev/null +++ b/indoteknik_custom/views/domain_apo.xml @@ -0,0 +1,46 @@ +<odoo> + <record id="view_apo_domain_config_tree" model="ir.ui.view"> + <field name="name">apo.domain.config.tree</field> + <field name="model">apo.domain.config</field> + <field name="arch" type="xml"> + <tree> + <field name="name"/> + <field name="vendor_id"/> + <field name="brand_ids"/> + <field name="is_special"/> + <field name="payment_term_id"/> + </tree> + </field> + </record> + + <record id="view_apo_domain_config_form" model="ir.ui.view"> + <field name="name">apo.domain.config.form</field> + <field name="model">apo.domain.config</field> + <field name="arch" type="xml"> + <form> + <sheet> + <group> + <field name="name"/> + <field name="vendor_id"/> + <field name="brand_ids"/> + <field name="is_special"/> + <field name="payment_term_id"/> + </group> + </sheet> + </form> + </field> + </record> + + <record id="domain_apo_action" model="ir.actions.act_window"> + <field name="name">Domain APO</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">apo.domain.config</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem id="menu_automatic_purchase" + name="Domain APO" + action="domain_apo_action" + parent="menu_monitoring_in_purchase" + sequence="200"/> +</odoo> diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 8d56bbbd..82daa36f 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -7,6 +7,12 @@ <field name="inherit_id" ref="sale.view_order_form"/> <field name="arch" type="xml"> <button id="action_confirm" position="after"> + <button name="action_open_partial_delivery_wizard" + string="Partial Delivery" + type="object" + class="oe_highlight" + attrs="{'invisible': [('state','!=','sale')]}"/> + <button name="calculate_line_no" string="Create No" type="object" @@ -365,9 +371,13 @@ </field> <field name="payment_term_id" position="attributes"> <attribute name="attrs"> - {'readonly': ['|', ('approval_status', 'in', ['pengajuan1', 'pengajuan2', 'approved']), - ('state', 'not in', - ['cancel', 'draft'])]} + { + 'readonly': [ + '|', + ('approval_status', 'in', ['pengajuan1', 'pengajuan2', 'approved']), + ('state', 'not in', ['cancel', 'draft']) + ] + } </attribute> </field> @@ -502,6 +512,44 @@ </field> </record> + <record id="view_partial_delivery_wizard_form" model="ir.ui.view"> + <field name="name">partial.delivery.wizard.form</field> + <field name="model">partial.delivery.wizard</field> + <field name="arch" type="xml"> + <form string="Partial Delivery Wizard"> + <group> + <!-- Field ini WAJIB ada walau invisible --> + <field name="picking_ids" invisible="1"/> + + <field name="picking_id"/> + </group> + + <separator string="Products"/> + <div class="oe_button_box" name="select_all_box"> + <button name="action_select_all" string="✅ Select All" type="object" class="btn-primary"/> + <button name="action_unselect_all" string="❌ Unselect All" type="object" class="btn-secondary"/> + </div> + + <field name="line_ids" context="{'default_wizard_id': active_id}" widget="many2many"> + <tree editable="bottom"> + <field name="selected"/> + <field name="product_id"/> + <field name="ordered_qty" readonly="1"/> + <field name="reserved_qty" readonly="1"/> + <field name="selected_qty"/> + </tree> + </field> + + <footer> + <button string="Confirm" type="object" name="action_confirm_partial_delivery" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> + + + <record id="sale_order_multi_update_ir_actions_server" model="ir.actions.server"> <field name="name">Mark As Cancel</field> <field name="model_id" ref="sale.model_sale_order"/> diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index d943f27a..44ab6355 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -207,6 +207,7 @@ <field name="product_uom" position="after"> <field name="sale_id" attrs="{'readonly': 1}" optional="hide"/> <field name="print_barcode" optional="hide"/> + <field name="partial" widget="boolean_toggle" optional="hide"/> <field name="qr_code_variant" widget="image" optional="hide"/> <field name="barcode" optional="hide"/> </field> |
