summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models/partial_delivery.py
diff options
context:
space:
mode:
authorFIN-IT_AndriFP <it@fixcomart.co.id>2025-10-10 14:49:13 +0700
committerFIN-IT_AndriFP <it@fixcomart.co.id>2025-10-10 14:49:13 +0700
commit10ddd8835a98bbfe58abedf6a405929dfbbb76d0 (patch)
treea61cea71a2e732a70d20fcbcc271d79e69ded02c /indoteknik_custom/models/partial_delivery.py
parentb0d0e26965ab1ba7aa0c5a607cc0a12a67546dfc (diff)
parente4b191155bf44bfcd58d6ae1b95d4a112bd43547 (diff)
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into odoo-backup
Diffstat (limited to 'indoteknik_custom/models/partial_delivery.py')
-rw-r--r--indoteknik_custom/models/partial_delivery.py214
1 files changed, 214 insertions, 0 deletions
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")
+