diff options
| author | Azka Nathan <darizkyfaz@gmail.com> | 2024-09-30 14:08:26 +0700 |
|---|---|---|
| committer | Azka Nathan <darizkyfaz@gmail.com> | 2024-09-30 14:08:26 +0700 |
| commit | 3b83868fbdb29e8f5208035e5166a13e5d24f382 (patch) | |
| tree | 437eae685994f9f85682fe53805a476b45164d6d | |
| parent | e48193b793216a4ab82d88753ae144c08c3ddfaf (diff) | |
| parent | 5e1a6dc4d2bb04a36fcaef023fb9894336ebd4f6 (diff) | |
Merge branch 'unreserved_permission' into production
# Conflicts:
# indoteknik_custom/models/__init__.py
# indoteknik_custom/security/ir.model.access.csv
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 1 | ||||
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 1 | ||||
| -rw-r--r-- | indoteknik_custom/models/approval_unreserve.py | 146 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 1 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_move.py | 44 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_picking.py | 19 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 2 | ||||
| -rw-r--r-- | indoteknik_custom/views/approval_unreserve.xml | 81 | ||||
| -rw-r--r-- | indoteknik_custom/views/ir_sequence.xml | 10 | ||||
| -rwxr-xr-x | indoteknik_custom/views/sale_order.xml | 1 |
10 files changed, 303 insertions, 3 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 8d8e8cec..e1a67592 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -142,6 +142,7 @@ 'views/approval_date_doc.xml', 'views/partner_payment_term.xml', 'views/vendor_payment_term.xml', + 'views/approval_unreserve.xml', 'report/report.xml', 'report/report_banner_banner.xml', 'report/report_banner_banner2.xml', diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 4619147a..3d700ce0 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -127,3 +127,4 @@ from . import shipment_group from . import sales_order_reject from . import approval_date_doc from . import account_tax +from . import approval_unreserve diff --git a/indoteknik_custom/models/approval_unreserve.py b/indoteknik_custom/models/approval_unreserve.py new file mode 100644 index 00000000..88409c37 --- /dev/null +++ b/indoteknik_custom/models/approval_unreserve.py @@ -0,0 +1,146 @@ +from odoo import models, api, fields +from odoo.exceptions import AccessError, UserError, ValidationError +from datetime import timedelta, date +import logging + +_logger = logging.getLogger(__name__) + +class ApprovalUnreserve(models.Model): + _name = "approval.unreserve" + _description = "Approval Unreserve" + _inherit = ['mail.thread'] + _rec_name = 'number' + + number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True, default='New') + approval_line = fields.One2many('approval.unreserve.line', 'approval_id', string='Approval Unreserve Lines', auto_join=True) + state = fields.Selection([ + ('draft', 'Draft'), + ('waiting_approval', 'Waiting for Approval'), + ('approved', 'Approved'), + ('rejected', 'Rejected') + ], string="Status", default='draft', tracking=True) + request_date = fields.Date(string="Request Date", default=fields.Date.today, tracking=True) + approved_by = fields.Many2one('res.users', string="Approved By", readonly=True, tracking=True) + picking_id = fields.Many2one('stock.picking', string="Picking", tracking=True) + user_id = fields.Many2one('res.users', string="User", readonly=True, tracking=True) + rejection_reason = fields.Text(string="Rejection Reason", tracking=True) + reason = fields.Text(string="Reason", tracking=True) + + @api.constrains('picking_id') + def create_move_id_line(self): + if not self.picking_id: + raise ValidationError("Picking is required") + + stock_move = self.env['stock.move'].search([('picking_id', '=', self.picking_id.id), ('state', '=', 'assigned')]) + + if not stock_move: + raise ValidationError("Picking is not found") + + for move in stock_move: + self.approval_line.create({ + 'approval_id': self.id, + 'move_id': move.id + }) + + self.user_id = self.picking_id.sale_id.user_id.id + + @api.model + def create(self, vals): + if vals.get('number', 'New') == 'New': + vals['number'] = self.env['ir.sequence'].next_by_code('approval.unreserve') or 'New' + return super(ApprovalUnreserve, self).create(vals) + + def action_submit_for_approval(self): + self._check_product_and_qty() + self.write({'state': 'waiting_approval'}) + + + def _check_product_and_qty(self): + stock_move = self.env['stock.move'] + for line in self.approval_line: + if line.dest_picking_id: + move = stock_move.search([ + ('picking_id', '=', line.dest_picking_id.id), + ('product_id', '=', line.product_id.id), + ('state', 'not in', ['done', 'cancel']) + ]) + + if not move: + raise UserError("Product tidak ada di destination picking") + + qty_unreserve = line.unreserve_qty + move.forecast_availability + + if move.product_uom_qty < qty_unreserve: + raise UserError("Quantity yang di unreserve melebihi quantity yang ada") + + def action_approve(self): + if self.env.user.id != self.user_id.id: + raise UserError("Hanya Sales nya yang bisa approve.") + + if self.state != 'waiting_approval': + raise UserError("Approval can only be done in 'Waiting for Approval' state") + + self.write({ + 'state': 'approved', + 'approved_by': self.env.user.id + }) + # Trigger the unreserve function + self._trigger_unreserve() + + def action_reject(self, reason): + if self.env.user.id != self.user_id.id: + raise UserError("Hanya Sales nya yang bisa reject.") + + if self.state != 'waiting_approval': + raise UserError("Rejection can only be done in 'Waiting for Approval' state") + self.write({ + 'state': 'rejected', + 'rejection_reason': reason + }) + + def _trigger_unreserve(self): + stock_move_obj = self.env['stock.move'] + + for line in self.approval_line: + move = stock_move_obj.browse(line.move_id.id) + move._do_unreserve(product=line.product_id, quantity=line.unreserve_qty) + + original_sale_id = move.picking_id.sale_id + + product_name = line.product_id.display_name + unreserved_qty = line.unreserve_qty + + if line.dest_picking_id: + dest_sale_id = line.dest_picking_id.sale_id + line.dest_picking_id.action_assign() + + if original_sale_id: + message = ( + f"Barang {product_name} sebanyak {unreserved_qty} dipindahkan ke SO {dest_sale_id.name}" + if dest_sale_id else + f"Barang {product_name} sebanyak {unreserved_qty} dipindahkan ke picking tujuan." + ) + original_sale_id.message_post(body=message) + else: + if original_sale_id: + message = f"Barang {product_name} sebanyak {unreserved_qty} ter unreserve." + original_sale_id.message_post(body=message) + + +class ApprovalUnreserveLine(models.Model): + _name = 'approval.unreserve.line' + _description = 'Approval Unreserve Line' + _order = 'approval_id, id' + + approval_id = fields.Many2one('approval.unreserve', string='Approval Reference', required=True, ondelete='cascade', index=True, copy=False) + move_id = fields.Many2one('stock.move', string="Stock Move", required=True) + product_id = fields.Many2one('product.product', string="Product", related='move_id.product_id', readonly=True) + sales_id = fields.Many2one('res.users', string="Sales", readonly=True, tracking=True) + unreserve_qty = fields.Float(string="Quantity to Unreserve") + dest_picking_id = fields.Many2one('stock.picking', string="Destination Picking", tracking=True) + + + @api.onchange('dest_picking_id') + def onchange_dest_picking_id(self): + if self.dest_picking_id: + self.sales_id = self.dest_picking_id.sale_id.user_id.id diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 798b9109..d0a34007 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -111,6 +111,7 @@ class SaleOrder(models.Model): date_driver_departure = fields.Datetime(string='Departure Date', compute='_compute_date_kirim', copy=False) note_website = fields.Char(string="Note Website") use_button = fields.Boolean(string='Using Calculate Selling Price', copy=False) + unreserve_id = fields.Many2one('stock.picking', 'Unreserve Picking') voucher_shipping_id = fields.Many2one(comodel_name='voucher', string='Voucher Shipping', copy=False) margin_after_delivery_purchase = fields.Float(string='Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase') percent_margin_after_delivery_purchase = fields.Float(string='% Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase') diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index fe46bf65..ac2e3cc0 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -1,5 +1,6 @@ from odoo import fields, models, api - +from odoo.tools.misc import format_date, OrderedSet +from odoo.exceptions import UserError class StockMove(models.Model): _inherit = 'stock.move' @@ -7,6 +8,47 @@ class StockMove(models.Model): line_no = fields.Integer('No', default=0) sale_id = fields.Many2one('sale.order', string='SO') + def _do_unreserve(self, product=None, quantity=False): + moves_to_unreserve = OrderedSet() + for move in self: + if move.state == 'cancel' or (move.state == 'done' and move.scrapped): + continue + elif move.state == 'done': + raise UserError("You cannot unreserve a stock move that has been set to 'Done'.") + + if product and move.product_id != product: + continue # Skip moves that don't match the specified product + moves_to_unreserve.add(move.id) + + moves_to_unreserve = self.env['stock.move'].browse(moves_to_unreserve) + + ml_to_update, ml_to_unlink = OrderedSet(), OrderedSet() + moves_not_to_recompute = OrderedSet() + + for ml in moves_to_unreserve.move_line_ids: + if product and ml.product_id != product: + continue # Only affect the specified product + + if quantity and quantity > 0: + # Only reduce by the specified quantity if it is greater than zero + ml_to_update.add(ml.id) + remaining_qty = ml.product_uom_qty - quantity + ml.write({'product_uom_qty': remaining_qty if remaining_qty > 0 else 0}) + quantity = 0 # Set to zero to prevent further unreserving in the same loop + elif ml.qty_done: + ml_to_update.add(ml.id) + else: + ml_to_unlink.add(ml.id) + moves_not_to_recompute.add(ml.move_id.id) + + ml_to_update, ml_to_unlink = self.env['stock.move.line'].browse(ml_to_update), self.env['stock.move.line'].browse(ml_to_unlink) + moves_not_to_recompute = self.env['stock.move'].browse(moves_not_to_recompute) + + ml_to_unlink.unlink() + (moves_to_unreserve - moves_not_to_recompute)._recompute_state() + return True + + def _prepare_account_move_line_from_mr(self, po_line, qty, move=False): po_line.ensure_one() aml_currency = move and move.currency_id or po_line.currency_id diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3dd960e2..66a326ff 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -125,12 +125,27 @@ class StockPicking(models.Model): raise UserError('Hanya Logistic yang bisa mengubah shipping method') def do_unreserve(self): + if not self._context.get('darimana') == 'sale.order': + self.sale_id.unreserve_id = self.id + return self._create_approval_notification('Logistic') + res = super(StockPicking, self).do_unreserve() - if not self.env.user.is_purchasing_manager: - raise UserError('Hanya Purchasing Manager yang bisa Unreserve') current_time = datetime.datetime.utcnow() self.date_unreserve = current_time + return res + + def _create_approval_notification(self, approval_role): + title = 'Warning' + message = f'Butuh approval sales untuk unreserved' + return self._create_notification_action(title, message) + + def _create_notification_action(self, title, message): + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { 'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'} }, + } def _compute_shipping_status(self): for rec in self: diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 61ced26c..19e3bdca 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -136,3 +136,5 @@ access_shipment_group_line,access.shipment.group.line,model_shipment_group_line, access_sales_order_reject,access.sales.order.reject,model_sales_order_reject,,1,1,1,1 access_approval_date_doc,access.approval.date.doc,model_approval_date_doc,,1,1,1,1 access_account_tax,access.account.tax,model_account_tax,,1,1,1,1 +access_approval_unreserve,access.approval.unreserve,model_approval_unreserve,,1,1,1,1 +access_approval_unreserve_line,access.approval.unreserve.line,model_approval_unreserve_line,,1,1,1,1 diff --git a/indoteknik_custom/views/approval_unreserve.xml b/indoteknik_custom/views/approval_unreserve.xml new file mode 100644 index 00000000..317b5276 --- /dev/null +++ b/indoteknik_custom/views/approval_unreserve.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <record id="view_approval_unreserve_tree" model="ir.ui.view"> + <field name="name">approval.unreserve.tree</field> + <field name="model">approval.unreserve</field> + <field name="arch" type="xml"> + <tree string="Approval Unreserve"> + <field name="number"/> + <field name="request_date"/> + <field name="picking_id"/> + <field name="state"/> + <field name="user_id"/> + </tree> + </field> + </record> + + <record id="approval_unreserve_line_tree" model="ir.ui.view"> + <field name="name">approval.unreserve.line.tree</field> + <field name="model">approval.unreserve.line</field> + <field name="arch" type="xml"> + <tree editable="bottom"> + <field name="move_id"/> + <field name="product_id"/> + <field name="dest_picking_id"/> + <field name="sales_id"/> + <field name="unreserve_qty"/> + </tree> + </field> + </record> + + <record id="approval_unreserve_form" model="ir.ui.view"> + <field name="name">approval.unreserve.form</field> + <field name="model">approval.unreserve</field> + <field name="arch" type="xml"> + <form create="false"> + <header> + <button name="action_submit_for_approval" type="object" string="Submit for Approval" attrs="{'invisible': [('state', '!=', 'draft')]}"/> + <button name="action_approve" type="object" string="Approve" attrs="{'invisible': [('state', '!=', 'waiting_approval')]}"/> + <button name="action_reject" type="object" string="Reject" attrs="{'invisible': [('state', '!=', 'waiting_approval')]}"/> + </header> + <sheet> + <group> + <group> + <field name="number" readonly="1"/> + <field name="request_date" readonly="1"/> + <field name="picking_id"/> + <field name="user_id" readonly="1"/> + <field name="state" readonly="1"/> + <field name="approved_by" readonly="1"/> + <field name="reason"/> + </group> + </group> + <notebook> + <page string="Lines"> + <field name="approval_line"/> + </page> + </notebook> + </sheet> + <div class="oe_chatter"> + <field name="message_follower_ids" widget="mail_followers"/> + <field name="message_ids" widget="mail_thread"/> + </div> + </form> + </field> + </record> + + <record id="approval_unreserve_action" model="ir.actions.act_window"> + <field name="name">Approval Unreserve</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">approval.unreserve</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem + id="menu_approval_unreserve" + name="Approval Unreserve" + parent="sale.product_menu_catalog" + sequence="4" + action="approval_unreserve_action" + /> +</odoo> diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index b2768c71..dd501d8c 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -20,6 +20,16 @@ <field name="number_next">1</field> <field name="number_increment">1</field> </record> + + <record id="sequence_approval_unreserve" model="ir.sequence"> + <field name="name">Approval Unreserve</field> + <field name="code">approval.unreserve</field> + <field name="active">TRUE</field> + <field name="prefix">AU/%(year)s/</field> + <field name="padding">5</field> + <field name="number_next">1</field> + <field name="number_increment">1</field> + </record> <record id="sequence_logbook_sj" model="ir.sequence"> <field name="name">Logbook SJ</field> diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 5ef399c7..17faaa95 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -78,6 +78,7 @@ <field name="npwp" placeholder='99.999.999.9-999.999' required="1"/> <field name="sppkp" attrs="{'required': [('customer_type', '=', 'pkp')]}"/> <field name="email" required="1"/> + <field name="unreserve_id"/> <field name="due_id" readonly="1"/> <field name="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1"/> <button name="override_allow_create_invoice" |
