summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAzka Nathan <darizkyfaz@gmail.com>2025-10-08 14:55:04 +0700
committerAzka Nathan <darizkyfaz@gmail.com>2025-10-08 14:55:04 +0700
commitc5642f4f6c4f0969475d863bee7243a83b9290dc (patch)
tree06e8a41b7f752d2b21d9ee8b93d5bf7fd32c2a72
parent3eb010cbf141d02fb3ea3812024a2b678fcb9f48 (diff)
partial
-rwxr-xr-xindoteknik_custom/__manifest__.py1
-rwxr-xr-xindoteknik_custom/models/__init__.py2
-rw-r--r--indoteknik_custom/models/automatic_purchase.py161
-rw-r--r--indoteknik_custom/models/domain_apo.py12
-rw-r--r--indoteknik_custom/models/partial_delivery.py189
-rwxr-xr-xindoteknik_custom/models/sale_order.py13
-rw-r--r--indoteknik_custom/models/stock_move.py1
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv3
-rw-r--r--indoteknik_custom/views/domain_apo.xml46
-rwxr-xr-xindoteknik_custom/views/sale_order.xml48
-rw-r--r--indoteknik_custom/views/stock_picking.xml1
11 files changed, 394 insertions, 83 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index b083be70..d1229ffe 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -186,6 +186,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 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"<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..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):
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index a1adc90a..a6175b21 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
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..eeb51eaa 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,14 @@
</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']),
+ ('partner_id', 'not in', [29179])
+ ]
+ }
</attribute>
</field>
@@ -502,6 +513,37 @@
</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">
+ <sheet>
+ <group>
+ <field name="sale_id" invisible="1"/>
+ <field name="picking_ids" invisible="1"/>
+ <field name="picking_id" options="{'no_create': True, 'no_create_edit': True}" />
+ <field name="line_ids" options="{'no_create': True, 'no_create_edit': True}">
+ <tree editable="bottom">
+ <field name="selected"/>
+ <field name="product_id" readonly="1" force_save="1"/>
+ <field name="move_id" readonly="1" force_save="1"/>
+ <field name="ordered_qty"/>
+ <field name="reserved_qty" readonly="1" force_save="1"/>
+ <field name="selected_qty" invisible="0"/>
+ </tree>
+ </field>
+ </group>
+ </sheet>
+ <footer>
+ <button string="Confirm" type="object" name="action_confirm_partial_delivery" class="btn-primary"/>
+ <button string="Cancel" 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>