diff options
| author | trisusilo48 <tri.susilo@altama.co.id> | 2024-12-05 14:47:42 +0700 |
|---|---|---|
| committer | trisusilo48 <tri.susilo@altama.co.id> | 2024-12-05 14:47:42 +0700 |
| commit | 1e9e0c28ee4dc63f1465affac6d98ae213d9f283 (patch) | |
| tree | e48922d3885f3c1870f24cfad2c708389468d722 /indoteknik_custom/models | |
| parent | 4d5a0a6e2d997e323f8670172226f613b7673d62 (diff) | |
| parent | 4eac4d0709ec5d9e03b517a39cb67acbc35e2932 (diff) | |
Merge branch 'production' of https://bitbucket.org/altafixco/indoteknik-addons into production
Diffstat (limited to 'indoteknik_custom/models')
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 2 | ||||
| -rwxr-xr-x | indoteknik_custom/models/product_template.py | 82 | ||||
| -rw-r--r-- | indoteknik_custom/models/promotion/sale_order.py | 11 | ||||
| -rwxr-xr-x | indoteknik_custom/models/purchase_order.py | 34 | ||||
| -rw-r--r-- | indoteknik_custom/models/purchasing_job.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/res_partner.py | 12 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 56 | ||||
| -rw-r--r-- | indoteknik_custom/models/sale_order_line.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_picking.py | 113 | ||||
| -rw-r--r-- | indoteknik_custom/models/va_multi_approve.py | 22 | ||||
| -rw-r--r-- | indoteknik_custom/models/va_multi_reject.py | 22 | ||||
| -rw-r--r-- | indoteknik_custom/models/vendor_approval.py | 103 |
12 files changed, 365 insertions, 96 deletions
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 4983cc17..ad6d75dd 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -132,3 +132,5 @@ from . import vendor_approval from . import partner from . import find_page from . import approval_retur_picking +from . import va_multi_approve +from . import va_multi_reject diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 25473ab8..6fb8c7a0 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -1,4 +1,4 @@ -from odoo import fields, models, api +from odoo import fields, models, api, tools, _ from datetime import datetime, timedelta, date from odoo.exceptions import UserError import logging @@ -61,11 +61,12 @@ class ProductTemplate(models.Model): sni = fields.Boolean(string='SNI') tkdn = fields.Boolean(string='TKDN') short_spesification = fields.Char(string='Short Spesification') + merchandise_ok = fields.Boolean(string='Product Promotion') @api.constrains('name', 'internal_reference', 'x_manufacture') def required_public_categ_ids(self): for rec in self: - if not rec.public_categ_ids: + if not rec.public_categ_ids and rec.type == 'product': raise UserError('Field Categories harus diisi') def _get_qty_sold(self): @@ -377,6 +378,7 @@ class ProductProduct(models.Model): max_qty_reorder = fields.Float(string='Max Qty Reorder', compute='_get_max_qty_reordering_rule') qty_rpo = fields.Float(string='Qty RPO', compute='_get_qty_rpo') plafon_qty = fields.Float(string='Max Plafon', compute='_get_plafon_qty_product') + merchandise_ok = fields.Boolean(string='Product Promotion') def _get_clean_website_description(self): for rec in self: @@ -386,7 +388,7 @@ class ProductProduct(models.Model): @api.constrains('name', 'internal_reference', 'x_manufacture') def required_public_categ_ids(self): for rec in self: - if not rec.public_categ_ids: + if not rec.public_categ_ids and rec.type == 'product': raise UserError('Field Categories harus diisi') @api.constrains('active') @@ -482,33 +484,38 @@ class ProductProduct(models.Model): def _get_qty_incoming_bandengan(self): for product in self: - qty_incoming = self.env['stock.move'].search([ - ('product_id', '=', product.id), - ('location_dest_id', 'in', [57, 83]), - ('state', 'not in', ['done', 'cancel']) - ]) - qty = sum(qty_incoming.mapped('product_uom_qty')) + qty = self.env['v.move.outstanding'].read_group( + domain=[ + ('product_id', '=', product.id), + ('location_dest_id', 'in', [57, 83]), + ], + fields=['qty_need'], + groupby=[] + )[0].get('qty_need', 0.0) product.qty_incoming_bandengan = qty def _get_qty_incoming_bandengan_with_exclude(self): for product in self: - qty_incoming = self.env['stock.move'].search([ - ('product_id', '=', product.id), - ('location_dest_id', 'in', [57, 83]), - ('state', 'not in', ['done', 'cancel']) - ]) - qty = sum(qty_incoming.mapped('product_uom_qty')) + qty = self.env['v.move.outstanding'].read_group( + domain=[ + ('product_id', '=', product.id), + ('location_dest_id', 'in', [57, 83]), + ], + fields=['qty_need'], + groupby=[] + )[0].get('qty_need', 0.0) product.qty_incoming_bandengan = qty def _get_qty_outgoing_bandengan(self): for product in self: - qty_incoming = self.env['stock.move'].search([ - ('product_id', '=', product.id), - ('location_dest_id', '=', 5), - ('location_id', 'in', [57, 83]), - ('state', 'not in', ['done', 'cancel']) - ]) - qty = sum(qty_incoming.mapped('product_uom_qty')) + qty = self.env['v.move.outstanding'].read_group( + domain=[ + ('product_id', '=', product.id), + ('location_id', 'in', [57, 83]), + ], + fields=['qty_need'], + groupby=[] + )[0].get('qty_need', 0.0) product.qty_outgoing_bandengan = qty def _get_qty_onhand_bandengan(self): @@ -598,3 +605,34 @@ class ProductProduct(models.Model): ('end_date', '>=', current_time) ], limit=1) return pricelist + + +class OutstandingMove(models.Model): + _name = 'v.move.outstanding' + _auto = False + _rec_name = 'id' + + id = fields.Integer(string='ID') + product_id = fields.Many2one('product.product', string='Product') + reference = fields.Char(string='Reference', help='Nomor Dokumen terkait') + qty_need = fields.Float(string='Qty Need', help='Qty yang akan outgoing / incoming') + location_id = fields.Many2one('stock.location', string='Location', help='Lokasi asal') + location_dest_id = fields.Many2one('stock.location', string='Location To', help='Lokasi tujuan') + + def init(self): + # where clause 'state in' follow the origin of outgoing and incoming odoo + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute(""" + CREATE OR REPLACE VIEW %s AS + select sm.id, sm.reference, sm.product_id, + sm.product_uom_qty as qty_need, + sm.location_id, sm.location_dest_id + from stock_move sm + where 1=1 + and sm.state in( + 'waiting', + 'confirmed', + 'assigned', + 'partially_available' + ) + """ % self._table) diff --git a/indoteknik_custom/models/promotion/sale_order.py b/indoteknik_custom/models/promotion/sale_order.py index be820c6f..1c31d060 100644 --- a/indoteknik_custom/models/promotion/sale_order.py +++ b/indoteknik_custom/models/promotion/sale_order.py @@ -10,11 +10,12 @@ class SaleOrder(models.Model): for promotion in promotions: program_line = self.env['promotion.program.line'].browse(promotion['program_line_id']) for free_product in program_line.free_product_ids: - self.env['sale.order.line'].create({ - 'order_id': self.id, - 'name': "Free Product " + free_product.product_id.display_name, - 'display_type': 'line_note' - }) + if free_product.product_id.merchandise_ok: + self.env['sale.order.line'].create({ + 'order_id': self.id, + 'name': f"Free Product {free_product.product_id.display_name} Quantity ({free_product.qty})", + 'display_type': 'line_note' + }) def apply_promotion_program(self): userdata = { diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 3397616d..9388ae4c 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -595,10 +595,19 @@ class PurchaseOrder(models.Model): if line.product_uom_qty > line.product_id.plafon_qty + line.product_uom_qty and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): raise UserError('Product '+line.product_id.name+' melebihi plafon, harus Approval MD') + def check_different_vendor_so_po(self): + vendor_po = self.partner_id.id + for line in self.order_line: + if not line.so_line_id: + continue + if line.so_line_id.vendor_id.id != vendor_po and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError("Produk "+line.product_id.name+" memiliki vendor berbeda dengan SO (Vendor PO: "+str(self.partner_id.name)+", Vendor SO: "+str(line.so_line_id.vendor_id.name)+")") + def button_confirm(self): res = super(PurchaseOrder, self).button_confirm() current_time = datetime.now() self.check_ppn_mix() + self.check_different_vendor_so_po() # self.check_data_vendor() if self.amount_untaxed >= 50000000 and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): @@ -736,12 +745,12 @@ class PurchaseOrder(models.Model): template.send_mail(self.id, force_send=True) def po_approve(self): - # if self.amount_untaxed >= 50000000 and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): - # raise UserError("Hanya Merchandiser yang bisa approve") greater_than_plafon, message = self._get_msg_plafon_qty() + different_vendor_message = self.check_different_vendor_so() # Panggil fungsi check_different_vendor_so + if self.env.user.is_leader or self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): raise UserError("Bisa langsung Confirm") - elif self.total_percent_margin == self.total_so_percent_margin and self.matches_so and not greater_than_plafon: + elif self.total_percent_margin == self.total_so_percent_margin and self.matches_so and not greater_than_plafon and not different_vendor_message: raise UserError("Bisa langsung Confirm") else: reason = '' @@ -757,13 +766,30 @@ class PurchaseOrder(models.Model): # Check Plafon Qty and Get Message every Line Product if greater_than_plafon: reason += message + # Check for Different Vendor Message + if different_vendor_message: + reason += different_vendor_message + # Post a highlighted message to lognote self.message_post( body=f"<div style='background-color: #fdf2e9; border: 1px solid #f5c6cb; padding: 10px;'>" - f"<b>Note (Pinned):</b><br>{reason}</div>", + f"<b>Note (Pinned):</b><br>{reason}</div>", subtype_id=self.env.ref("mail.mt_note").id ) + + def check_different_vendor_so(self): + vendor_po = self.partner_id.id + message = '' + for line in self.order_line: + if not line.so_line_id: + continue + if line.so_line_id.vendor_id.id != vendor_po: + product_code = line.product_id.display_name or 'Unknown' + message += (f"Produk {product_code} memiliki vendor berbeda dengan SO " + f"(Vendor PO: {self.partner_id.name}, Vendor SO: {line.so_line_id.vendor_id.name}), ") + return message if message else None + def _get_msg_plafon_qty(self): message = '' greater_than_plafon = False diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index bf5ed8c4..4efb0cd4 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -57,7 +57,7 @@ class PurchasingJob(models.Model): max(pjs.status_apo::text) AS status_apo, max(pjs.note::text) AS note, CASE - WHEN sub.vendor_id = 5571 THEN 27 + WHEN pmp.brand IN ('Tekiro', 'RYU', 'Rexco') THEN 27 WHEN sub.vendor_id = 9688 THEN 397 WHEN sub.vendor_id = 35475 THEN 397 WHEN sub.vendor_id = 29712 THEN 397 diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 4297012c..da4a6cb6 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -1,7 +1,7 @@ from odoo import models, fields, api from odoo.exceptions import UserError, ValidationError from datetime import datetime - +from odoo.http import request class GroupPartner(models.Model): _name = 'group.partner' @@ -244,6 +244,14 @@ class ResPartner(models.Model): def _onchange_customer_type(self): if self.customer_type == 'nonpkp': self.npwp = '00.000.000.0-000.000' - + def get_check_tempo_partner(self): + self.ensure_one() + + partner = self.parent_id or self + + if not partner.property_payment_term_id or 'Tempo' not in partner.property_payment_term_id.name: + return False + else: + return True diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 5e868edd..7fc6d96a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -79,7 +79,7 @@ class SaleOrder(models.Model): payment_link_midtrans = fields.Char(string='Payment Link', help='Url payment yg digenerate oleh midtrans, harap diserahkan ke customer agar dapat dilakukan pembayaran secara mandiri') payment_qr_code = fields.Binary("Payment QR Code") due_id = fields.Many2one('due.extension', string="Due Extension", readonly=True, tracking=True) - vendor_approval_id = fields.Many2one('vendor.approval', string="Vendor Approval", readonly=True, tracking=True, copy=False) + vendor_approval_id = fields.Many2many('vendor.approval', string="Vendor Approval", readonly=True, tracking=True, copy=False) customer_type = fields.Selection([ ('pkp', 'PKP'), ('nonpkp', 'Non PKP') @@ -99,6 +99,7 @@ class SaleOrder(models.Model): helper_by_id = fields.Many2one('res.users', 'Helper By') eta_date = fields.Datetime(string='ETA Date', copy=False, compute='_compute_eta_date') flash_sale = fields.Boolean(string='Flash Sale', help='Data dari web') + is_continue_transaction = fields.Boolean(string='Button Transaction', help='Data dari web') web_approval = fields.Selection([ ('company', 'Company'), ('cust_manager', 'Customer Manager'), @@ -144,6 +145,14 @@ class SaleOrder(models.Model): ('NP', 'Non Pareto') ]) + @api.onchange('payment_status') + def _is_continue_transaction(self): + if not self.is_continue_transaction: + if self.payment_status == 'settlement': + self.is_continue_transaction = True + else: + self.is_continue_transaction = False + def _compute_total_weight(self): total_weight = 0 missing_weight_products = [] @@ -882,29 +891,26 @@ class SaleOrder(models.Model): }).send() def validate_different_vendor(self): - if self.vendor_approval_id and self.vendor_approval_id.state == 'draft': - raise UserError('SO ini sedang dalam review Vendor Approval') - - if self.vendor_approval_id and self.vendor_approval_id.state == 'cancel': - raise UserError('Vendor Approval SO ini Di Reject') - - if self.vendor_approval_id and self.vendor_approval_id.state == 'done': + if self.vendor_approval_id.filtered(lambda v: v.state == 'draft'): + draft_names = ", ".join(self.vendor_approval_id.filtered(lambda v: v.state == 'draft').mapped('number')) + raise UserError(f"SO ini sedang dalam review Vendor Approval: {draft_names}") + + if self.vendor_approval_id and all(v.state != 'draft' for v in self.vendor_approval_id): return False - different_vendor = self.order_line.filtered(lambda l: l.vendor_id and l.vendor_md_id and l.vendor_id.id != l.vendor_md_id.id) + different_vendor = self.order_line.filtered( + lambda l: l.vendor_id and l.vendor_md_id and l.vendor_id.id != l.vendor_md_id.id + ) + if different_vendor: - vendor_approval = self.env['vendor.approval'].create({ - 'order_id': self.id, - 'create_date_so': self.create_date, - 'partner_id': self.partner_id.id, - 'state': 'draft', - }) - - self.vendor_approval_id = vendor_approval.id - + vendor_approvals = [] for line in different_vendor: - self.env['vendor.approval.line'].create({ - 'vendor_approval_id': vendor_approval.id, + vendor_approval = self.env['vendor.approval'].create({ + 'order_id': self.id, + 'order_line_id': line.id, + 'create_date_so': self.create_date, + 'partner_id': self.partner_id.id, + 'state': 'draft', 'product_id': line.product_id.id, 'product_uom_qty': line.product_uom_qty, 'vendor_id': line.vendor_id.id, @@ -916,9 +922,15 @@ class SaleOrder(models.Model): 'margin_after': line.item_percent_margin, 'purchase_tax_id': line.purchase_tax_id.id, 'sales_tax_id': line.tax_id[0].id if line.tax_id else False, - 'percent_margin_difference': (line.price_unit - line.purchase_price_md) / line.purchase_price_md if line.purchase_price_md else False, + 'percent_margin_difference': ( + (line.price_unit - line.purchase_price_md) / line.purchase_price_md + if line.purchase_price_md else False + ), }) - + + vendor_approvals.append(vendor_approval.id) + + self.vendor_approval_id = [(4, vid) for vid in vendor_approvals] return True else: return False diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 04fafa69..a31ff569 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -33,7 +33,7 @@ class SaleOrderLine(models.Model): qty_reserved = fields.Float(string='Qty Reserved', compute='_compute_qty_reserved') product_available_quantity = fields.Float(string='Qty pickup by user',) reserved_from = fields.Char(string='Reserved From', copy=False) - item_percent_margin_without_deduction = fields.Float('%Margin', compute='_compute_item_margin_without_deduction') + item_percent_margin_without_deduction = fields.Float('Margin Without Deduction', compute='_compute_item_margin_without_deduction') weight = fields.Float(string='Weight') md_vendor_id = fields.Many2one('res.partner', string='MD Vendor', readonly=True) margin_md = fields.Float(string='Margin MD') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 17dd5766..931da588 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1,9 +1,11 @@ from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools.float_utils import float_is_zero -from datetime import datetime +from datetime import timedelta from itertools import groupby -import pytz, datetime, requests, json +import pytz, requests, json, requests +from dateutil import parser +import datetime class StockPicking(models.Model): @@ -113,6 +115,113 @@ class StockPicking(models.Model): ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") notee = fields.Text(string="Note") + # Envio Tracking Section + envio_id = fields.Char(string="Envio ID", readonly=True) + envio_code = fields.Char(string="Envio Code", readonly=True) + envio_ref_code = fields.Char(string="Envio Reference Code", readonly=True) + envio_eta_at = fields.Char(string="Estimated Time of Arrival (ETA)", readonly=True) + envio_ata_at = fields.Char(string="Actual Time of Arrival (ATA)", readonly=True) + envio_etd_at = fields.Char(string="Estimated Time of Departure (ETD)", readonly=True) + envio_atd_at = fields.Char(string="Actual Time of Departure (ATD)", readonly=True) + envio_received_by = fields.Char(string="Received By", readonly=True) + envio_status = fields.Char(string="Status", readonly=True) + envio_cod_value = fields.Float(string="COD Value", readonly=True) + envio_cod_status = fields.Char(string="COD Status", readonly=True) + envio_logs = fields.Text(string="Logs", readonly=True) + envio_latest_message = fields.Text(string="Latest Log Message", readonly=True) + envio_latest_recorded_at = fields.Char(string="Log Recorded At", readonly=True) + envio_latest_latitude = fields.Float(string="Log Latitude", readonly=True) + envio_latest_longitude = fields.Float(string="Log Longitude", readonly=True) + tracking_by = fields.Many2one('res.users', string='Tracking By', readonly=True, tracking=True) + + def _convert_to_wib(self, date_str): + """ + Mengonversi string waktu ISO 8601 ke format waktu Indonesia (WIB) + """ + if not date_str: + return False + try: + utc_time = datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%SZ') + wib_time = utc_time + timedelta(hours=7) + return wib_time.strftime('%d-%m-%Y %H:%M:%S') + except ValueError: + raise UserError(f"Format waktu tidak sesuai: {date_str}") + + def _convert_to_datetime(self, date_str): + """Mengonversi string waktu dari API ke datetime.""" + if not date_str: + return False + try: + # Format waktu dengan milidetik + return datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%S.%fZ') + except ValueError: + try: + # Format waktu tanpa milidetik + return datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%SZ') + except ValueError: + raise UserError(f"Format waktu tidak sesuai: {date_str}") + + def track_envio_shipment(self): + pickings = self.env['stock.picking'].search([ + ('picking_type_code', '=', 'outgoing'), + ('state', '=', 'done'), + ('carrier_id', '=', 151) + ]) + for picking in self: + if not picking.name: + raise UserError("Name pada stock.picking tidak ditemukan.") + + # API URL dan headers + url = f"https://api.envio.co.id/v1/tracking/distribution?code={picking.name}" + headers = { + 'Authorization': 'Bearer JZ0Seh6qpYJAC3CJHdhF7sPqv8B/uSSfZe1VX5BL?vPYdo', + 'Content-Type': 'application/json', + } + + try: + # Request ke API + response = requests.get(url, headers=headers, timeout=10) + response.raise_for_status() # Raise error jika status code bukan 200 + response_data = response.json() + + # Validasi jika respons tidak sesuai format yang diharapkan + if not response_data or "data" not in response_data: + raise UserError("Respons API tidak sesuai format yang diharapkan.") + + data = response_data.get("data") + if not data: + continue + + # Menyimpan data ke field masing-masing + picking.envio_id = data.get("id") + picking.envio_code = data.get("code") + picking.envio_ref_code = data.get("ref_code") + picking.envio_eta_at = self._convert_to_datetime(data.get("eta_at")) + picking.envio_ata_at = self._convert_to_datetime(data.get("ata_at")) + picking.envio_etd_at = self._convert_to_datetime(data.get("etd_at")) + picking.envio_atd_at = self._convert_to_datetime(data.get("atd_at")) + picking.envio_received_by = data.get("received_by") + picking.envio_status = data.get("status") + picking.envio_cod_value = data.get("cod_value", 0.0) + picking.envio_cod_status = data.get("cod_status") + + # Menyimpan log terbaru + logs = data.get("logs", []) + if logs and isinstance(logs, list) and logs[0]: + latest_log = logs[0] + picking.envio_latest_message = latest_log.get("message", "Log kosong.") + picking.envio_latest_recorded_at = self._convert_to_datetime(latest_log.get("recorded_at")) + picking.envio_latest_latitude = latest_log.get("latitude", 0.0) + picking.envio_latest_longitude = latest_log.get("longitude", 0.0) + + picking.tracking_by = self.env.user.id + ata_at_str = data.get("ata_at") + picking.driver_arrival_date = self._convert_to_datetime(ata_at_str) if ata_at_str else False + except requests.exceptions.RequestException as e: + raise UserError(f"Terjadi kesalahan saat menghubungi API Envio: {str(e)}") + except Exception as e: + raise UserError(f"Kesalahan tidak terduga: {str(e)}") + def action_send_to_biteship(self): url = "https://api.biteship.com/v1/orders" api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" diff --git a/indoteknik_custom/models/va_multi_approve.py b/indoteknik_custom/models/va_multi_approve.py new file mode 100644 index 00000000..028358c2 --- /dev/null +++ b/indoteknik_custom/models/va_multi_approve.py @@ -0,0 +1,22 @@ +from odoo import models, fields +import logging + +_logger = logging.getLogger(__name__) + + +class VaMultiApprove(models.TransientModel): + _name = 'va.multi.approve' + + def save_multi_approve_va(self): + va_ids = self._context['va_ids'] + vendor_approval = self.env['vendor.approval'].browse(va_ids) + vendor_approval.action_approve() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Notification', + 'message': 'Berhasil Di Approve', + 'next': {'type': 'ir.actions.act_window_close'}, + } + }
\ No newline at end of file diff --git a/indoteknik_custom/models/va_multi_reject.py b/indoteknik_custom/models/va_multi_reject.py new file mode 100644 index 00000000..c05581bf --- /dev/null +++ b/indoteknik_custom/models/va_multi_reject.py @@ -0,0 +1,22 @@ +from odoo import models, fields +import logging + +_logger = logging.getLogger(__name__) + + +class VaMultiReject(models.TransientModel): + _name = 'va.multi.reject' + + def save_multi_reject_va(self): + va_ids = self._context['va_ids'] + vendor_approval = self.env['vendor.approval'].browse(va_ids) + vendor_approval.action_reject() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Notification', + 'message': 'Berhasil Di Reject', + 'next': {'type': 'ir.actions.act_window_close'}, + } + }
\ No newline at end of file diff --git a/indoteknik_custom/models/vendor_approval.py b/indoteknik_custom/models/vendor_approval.py index 56b81a37..01d2e6a2 100644 --- a/indoteknik_custom/models/vendor_approval.py +++ b/indoteknik_custom/models/vendor_approval.py @@ -13,61 +13,90 @@ class VendorApproval(models.Model): number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True) partner_id = fields.Many2one('res.partner', string="Customer", readonly=True) + order_line_id = fields.Many2one('sale.order.line', string="SO Line", readonly=True) order_id = fields.Many2one('sale.order', string="SO", readonly=True) - vendor_approval_line = fields.One2many('vendor.approval.line', 'vendor_approval_id', string='Vendor Approval Lines', auto_join=True) state = fields.Selection([('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Reject')], string='State', tracking=True) create_date_so = fields.Datetime(string='Create Date SO', readonly=True) + product_id = fields.Many2one('product.product', string='Product') + product_uom_qty = fields.Float(string='Quantity') + vendor_id = fields.Many2one('res.partner', string='Vendor') + vendor_md_id = fields.Many2one('res.partner', string='Vendor MD') + sales_price = fields.Float(string='Sales Price') + margin_before = fields.Float(string='Margin Before') + margin_after = fields.Float(string='Margin After') + purchase_price = fields.Float(string='Purchase Price') + purchase_price_md= fields.Float(string='Purchase Price MD') + purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) + sales_tax_id = fields.Many2one('account.tax', string='Sales Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) + percent_margin_difference = fields.Float(string='Percent Margin Difference') @api.model def create(self, vals): vals['number'] = self.env['ir.sequence'].next_by_code('vendor.approval') or '0' result = super(VendorApproval, self).create(vals) return result + + def check_state_so(self): + for rec in self: + if rec.order_id.state != 'draft': + raise UserError(f"SO {rec.order_id.name} sudah tidak bisa diubah") + def action_approve(self): - if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): - raise UserError('Hanya Merchandiser yang bisa approve') - - self.state = 'done' - self.order_id.vendor_approval = True - message = "Vendor Approval approved by %s" % (self.env.user.name) - self.order_id.message_post(body=message) - if not self.order_id.due_id and self.order_id.state == 'draft': - self.order_id.action_confirm() + for rec in self: + self.check_state_so() + if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError('Hanya Merchandiser yang bisa approve') + + # Set state menjadi 'done' + rec.state = 'done' + rec.order_id.vendor_approval = True + message = f"Vendor Approval approved by {self.env.user.name}" + rec.order_id.message_post(body=message) + + # Cek semua vendor.approval dengan order_id yang sama + related_approvals = self.env['vendor.approval'].search([('order_id', '=', rec.order_id.id)]) + if all(approval.state != 'draft' for approval in related_approvals): + # Jalankan action_confirm hanya jika semua state bukan draft + if not rec.order_id.due_id and rec.order_id.state == 'draft': + rec.order_id.action_confirm() + def action_reject(self): - if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): - raise UserError('Hanya Merchandiser yang bisa cancel') - - self.state = 'cancel' + for rec in self: + self.check_state_so() + if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError('Hanya Merchandiser yang bisa cancel') + + rec.state = 'cancel' + + rec.order_line_id.vendor_id = rec.vendor_md_id.id + rec.order_line_id.purchase_price = rec.purchase_price_md - message = "Vendor Approval rejected by %s" % (self.env.user.name) - self.order_id.message_post(body=message) + message = "Vendor Approval rejected by %s" % (self.env.user.name) + self.order_id.message_post(body=message) + + related_approvals = self.env['vendor.approval'].search([('order_id', '=', rec.order_id.id)]) + if all(approval.state != 'draft' for approval in related_approvals): + # Jalankan action_confirm hanya jika semua state bukan draft + if not rec.order_id.due_id and rec.order_id.state == 'draft': + rec.order_id.action_confirm() def unlink(self): res = super(VendorApproval, self).unlink() if not self._name == 'vendor.approval': raise UserError('Vendor Approval tidak bisa didelete') return res - - -class VendorApprovalLine(models.Model): - _name = 'vendor.approval.line' - _description = 'Vendor Approval Line' - _order = 'vendor_approval_id, id' - - vendor_approval_id = fields.Many2one('vendor.approval', string='Vendor Approval Ref', required=True, ondelete='cascade', index=True, copy=False) - product_id = fields.Many2one('product.product', string='Product') - product_uom_qty = fields.Float(string='Quantity') - vendor_id = fields.Many2one('res.partner', string='Vendor') - vendor_md_id = fields.Many2one('res.partner', string='Vendor MD') - sales_price = fields.Float(string='Sales Price') - margin_before = fields.Float(string='Margin Before') - margin_after = fields.Float(string='Margin After') - purchase_price = fields.Float(string='Purchase Price') - purchase_price_md= fields.Float(string='Purchase Price MD') - purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) - sales_tax_id = fields.Many2one('account.tax', string='Sales Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) - percent_margin_difference = fields.Float(string='Percent Margin Difference') - + def open_form_multi_approve(self): + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_va_multi_approve') + action['context'] = { + 'va_ids': [x.id for x in self] + } + return action + def open_form_multi_reject(self): + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_va_multi_reject') + action['context'] = { + 'va_ids': [x.id for x in self] + } + return action
\ No newline at end of file |
