diff options
| author | trisusilo48 <tri.susilo@altama.co.id> | 2025-04-14 16:50:07 +0700 |
|---|---|---|
| committer | trisusilo48 <tri.susilo@altama.co.id> | 2025-04-14 16:50:07 +0700 |
| commit | b3e915fa8d2f280d76ca1afb19e729804aeed6e6 (patch) | |
| tree | 3f728dfafc5ebbc597d42714faa5fcfaff8781d7 | |
| parent | 6eb0b48ad5c418f565efdf1a60d221a10465b0b8 (diff) | |
| parent | 3e22bea62b4c57268ce777d34ec6d19aede8b0c1 (diff) | |
Merge branch 'odoo-backup' into feature/feedback_bitehisp
# Conflicts:
# indoteknik_custom/security/ir.model.access.csv
22 files changed, 1085 insertions, 86 deletions
diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index d0cc055d..0a15d969 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -116,10 +116,10 @@ class StockPicking(controller.Controller): return self.response(picking.get_tracking_detail()) - @http.route(prefix + 'stock-picking/<picking_code>/documentation', auth='public', methods=['PUT', 'OPTIONS'], csrf=False) + @http.route(prefix + 'stock-picking/<id>/documentation', auth='public', methods=['PUT', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized() def write_partner_stock_picking_documentation(self, **kw): - picking_code = int(kw.get('picking_code', 0)) + id = int(kw.get('id', 0)) sj_document = kw.get('sj_document', False) paket_document = kw.get('paket_document', False) @@ -128,7 +128,7 @@ class StockPicking(controller.Controller): 'driver_arrival_date': datetime.utcnow(), } - picking_data = request.env['stock.picking'].search([('picking_code', '=', picking_code)], limit=1) + picking_data = request.env['stock.picking'].search([('id', '=', id)], limit=1) if not picking_data: return self.response(code=404, description='picking not found') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index d5cededa..06af8b61 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -145,6 +145,8 @@ from . import coretax_fatur from . import public_holiday from . import ir_actions_report from . import barcoding_product +from . import sales_order_koli +from . import stock_backorder_confirmation from . import account_payment_register from . import stock_inventory from . import sale_order_delay diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 45fdb8df..906985de 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -81,7 +81,7 @@ class AccountMove(models.Model): def compute_other_subtotal(self): for rec in self: - rec.other_subtotal = rec.amount_untaxed * (11 / 12) + rec.other_subtotal = round(rec.amount_untaxed * (11 / 12)) @api.model def generate_attachment(self, record): diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 0d31e954..6d832b85 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -1,7 +1,8 @@ -from odoo import models, api, fields +from odoo import models, api, fields, _ from odoo.exceptions import UserError from datetime import datetime import logging +from terbilang import Terbilang _logger = logging.getLogger(__name__) @@ -121,6 +122,21 @@ class CustomerRebate(models.Model): sum_dpp += invoice.price_subtotal return sum_dpp +class RejectReasonCommision(models.TransientModel): + _name = 'reject.reason.commision' + _description = 'Wizard for Reject Reason Customer Commision' + + request_id = fields.Many2one('customer.commision', string='Request') + reason_reject = fields.Text(string='Reason for Rejection', required=True, tracking=True) + + def confirm_reject(self): + commision = self.request_id + if commision: + commision.last_status = commision.status + commision.write({'reason_reject': self.reason_reject}) + commision.status = 'reject' + return {'type': 'ir.actions.act_window_close'} + class CustomerCommision(models.Model): _name = 'customer.commision' @@ -136,37 +152,79 @@ class CustomerCommision(models.Model): notification = fields.Char(string='Notification') commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', auto_join=True) status = fields.Selection([ - ('pengajuan1', 'Menunggu Approval Marketing'), - ('pengajuan2', 'Menunggu Approval Pimpinan'), - ('approved', 'Approved') - ], string='Status', copy=False, readonly=True, tracking=3) + ('pengajuan1', 'Menunggu Approval Manager Sales'), + ('pengajuan2', 'Menunggu Approval Marketing'), + ('pengajuan3', 'Menunggu Approval Pimpinan'), + ('pengajuan4', 'Menunggu Approval Accounting'), + ('approved', 'Approved'), + ('reject', 'Rejected'), + ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange',default='draft') + last_status = fields.Selection([ + ('pengajuan1', 'Menunggu Approval Manager Sales'), + ('pengajuan2', 'Menunggu Approval Marketing'), + ('pengajuan3', 'Menunggu Approval Pimpinan'), + ('pengajuan4', 'Menunggu Approval Accounting'), + ('approved', 'Approved'), + ('reject', 'Rejected'), + ], string='Status') commision_percent = fields.Float(string='Commision %', tracking=3) commision_amt = fields.Float(string='Commision Amount', tracking=3) + commision_amt_text = fields.Char(string='Commision Amount Text', compute='compute_delivery_amt_text') total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp') commision_type = fields.Selection([ ('fee', 'Fee'), ('cashback', 'Cashback'), ('rebate', 'Rebate'), ], string='Commision Type', required=True) - bank_name = fields.Char(string='Bank', tracking=3) - account_name = fields.Char(string='Account Name', tracking=3) - bank_account = fields.Char(string='Account No', tracking=3) + bank_name = fields.Char(string='Bank', tracking=3, required=True) + account_name = fields.Char(string='Account Name', tracking=3, required=True) + bank_account = fields.Char(string='Account No', tracking=3, required=True) note_transfer = fields.Char(string='Keterangan') brand_ids = fields.Many2many('x_manufactures', string='Brands') payment_status = fields.Selection([ ('pending', 'Pending'), ('payment', 'Payment'), ], string='Payment Status', copy=False, readonly=True, tracking=3, default='pending') + note_finnance = fields.Text('Notes Finnance') + reason_reject = fields.Char(string='Reason Reaject', tracking=True, track_visibility='onchange') + approved_by = fields.Char(string='Approved By', tracking=True, track_visibility='always') + grouped_so_number = fields.Char(string='Group SO Number', compute='_compute_grouped_numbers') grouped_invoice_number = fields.Char(string='Group Invoice Number', compute='_compute_grouped_numbers') + sales_id = fields.Many2one('res.users', string="Sales", tracking=True) + + date_approved_sales = fields.Datetime(string="Date Approved Sales", tracking=True) + date_approved_marketing = fields.Datetime(string="Date Approved Marketing", tracking=True) + date_approved_pimpinan = fields.Datetime(string="Date Approved Pimpinan", tracking=True) + date_approved_accounting = fields.Datetime(string="Date Approved Accounting", tracking=True) + + position_sales = fields.Char(string="Position Sales", tracking=True) + position_marketing = fields.Char(string="Position Marketing", tracking=True) + position_pimpinan = fields.Char(string="Position Pimpinan", tracking=True) + position_accounting = fields.Char(string="Position Accounting", tracking=True) + + def compute_delivery_amt_text(self): + tb = Terbilang() + + for record in self: + res = '' + + try: + if record.commision_amt > 0: + tb.parse(int(record.commision_amt)) + res = tb.getresult().title() + record.commision_amt_text = res + ' Rupiah' + except: + record.commision_amt_text = res + def _compute_grouped_numbers(self): for rec in self: so_numbers = set() invoice_numbers = set() for line in rec.commision_lines: - if line.invoice_id: + if line.invoice_id: if line.invoice_id.sale_id: so_numbers.add(line.invoice_id.sale_id.name) invoice_numbers.add(line.invoice_id.name) @@ -234,19 +292,50 @@ class CustomerCommision(models.Model): result = super(CustomerCommision, self).create(vals) return result - def action_confirm_customer_commision(self):#add 2 step approval - if not self.status: + def action_confirm_customer_commision(self): + now = datetime.utcnow() + if not self.status or self.status == 'draft': self.status = 'pengajuan1' - elif self.status == 'pengajuan1' and self.env.user.id == 19: + elif self.status == 'pengajuan1' and self.env.user.is_sales_manager: self.status = 'pengajuan2' - elif self.status == 'pengajuan2' and self.env.user.is_leader: + self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + self.date_approved_sales = now + self.position_sales = 'Sales Manager' + elif self.status == 'pengajuan2' and self.env.user.id == 19: + self.status = 'pengajuan3' + self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + self.date_approved_marketing = now + self.position_marketing = 'Marketing Manager' + elif self.status == 'pengajuan3' and self.env.user.is_leader: + self.status = 'pengajuan4' + self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + self.date_approved_pimpinan = now + self.position_pimpinan = 'Pimpinan' + elif self.status == 'pengajuan4' and self.env.user.id == 1272: for line in self.commision_lines: line.invoice_id.is_customer_commision = True self.status = 'approved' + self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + self.date_approved_accounting = now + self.position_accounting = 'Accounting' else: raise UserError('Harus di approved oleh yang bersangkutan') return + def action_reject(self):#add 2 step approval + return { + 'type': 'ir.actions.act_window', + 'name': _('Reject Reason'), + 'res_model': 'reject.reason.commision', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_request_id': self.id}, + } + + def button_draft(self): + for commision in self: + commision.status = commision.last_status if commision.last_status else 'draft' + def action_confirm_customer_payment(self): if self.status != 'approved': raise UserError('Commision harus di approve terlebih dahulu sebelum di konfirmasi pembayarannya') @@ -351,7 +440,9 @@ class CustomerCommisionLine(models.Model): tax = fields.Float(string='TaxAmt') total = fields.Float(string='Total') total_percent_margin = fields.Float('Total Margin', related='invoice_id.sale_id.total_percent_margin') + total_margin_excl_third_party = fields.Float('Before Margin', related='invoice_id.sale_id.total_margin_excl_third_party') product_id = fields.Many2one('product.product', string='Product') + sale_order_id = fields.Many2one('sale.order', string='Sale Order', related='invoice_id.sale_id') class AccountMove(models.Model): _inherit = 'account.move' diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 0bf98702..d80df2ce 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -1,4 +1,7 @@ -from odoo import fields, models, api, _ +from odoo import models, fields, api, tools, _ +from datetime import datetime, timedelta +import math +import logging from odoo.exceptions import AccessError, UserError, ValidationError @@ -7,6 +10,8 @@ class MrpProduction(models.Model): desc = fields.Text(string='Description') sale_order = fields.Many2one('sale.order', string='Sale Order', required=True, copy=False) + production_purchase_match = fields.One2many('production.purchase.match', 'production_id', string='Purchase Matches', auto_join=True) + is_po = fields.Boolean(string='Is PO') def action_confirm(self): """Override action_confirm untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'.""" @@ -20,4 +25,164 @@ class MrpProduction(models.Model): message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name) record.sale_order.message_post(body=message) - return result
\ No newline at end of file + return result + + + def create_po_from_manufacturing(self): + if not self.state == 'confirmed': + raise UserError('Harus Di Approve oleh Merchandiser') + + if self.is_po == True: + raise UserError('Sudah pernah di buat PO') + + if not self.move_raw_ids: + raise UserError('Tidak ada Lines, belum bisa create PO') + # if self.is_po: + # raise UserError('Sudah pernah di create PO') + + vendor_ids = self.env['stock.move'].read_group([ + ('raw_material_production_id', '=', self.id), + ('vendor_id', '!=', False) + ], fields=['vendor_id'], groupby=['vendor_id']) + + po_ids = [] + for vendor in vendor_ids: + result_po = self.create_po_by_vendor(vendor['vendor_id'][0]) + po_ids += result_po + return { + 'name': _('Purchase Order'), + 'view_mode': 'tree,form', + 'res_model': 'purchase.order', + 'target': 'current', + 'type': 'ir.actions.act_window', + 'domain': [('id', 'in', po_ids)], + } + + + def create_po_by_vendor(self, vendor_id): + current_time = datetime.now() + + PRODUCT_PER_PO = 20 + + stock_move = self.env['stock.move'] + + param_header = { + 'partner_id': vendor_id, + # 'partner_ref': self.sale_order_id.name, + '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, + 'product_bom_id': self.product_id.id, + # 'sale_order_id': self.sale_order_id.id, + 'note_description': 'from Manufacturing Order' + } + + domain = [ + ('raw_material_production_id', '=', self.id), + ('vendor_id', '=', vendor_id), + ('state', 'in', ['waiting','confirmed','partially_available']) + ] + + products_len = stock_move.search_count(domain) + page = math.ceil(products_len / PRODUCT_PER_PO) + po_ids = [] + # i start from zero (0) + for i in range(page): + new_po = self.env['purchase.order'].create([param_header]) + new_po.name = new_po.name + "/MO/" + str(i + 1) + po_ids.append(new_po.id) + lines = stock_move.search( + domain, + offset=i * PRODUCT_PER_PO, + limit=PRODUCT_PER_PO + ) + tax = [22] + + for line in lines: + product = line.product_id + price, taxes, vendor = self._get_purchase_price(product) + + param_line = { + 'order_id' : new_po.id, + 'product_id': product.id, + 'product_qty': line.product_uom_qty if line.state in ['confirmed', 'waiting'] else line.product_uom_qty - line.forecast_availability, + 'product_uom_qty': line.product_uom_qty if line.state in ['confirmed', 'waiting'] else line.product_uom_qty - line.forecast_availability, + 'name': product.display_name, + 'price_unit': price if price else 0.0, + 'taxes_id': [taxes] if taxes else [], + } + new_po_line = self.env['purchase.order.line'].create([param_line]) + + self.env['production.purchase.match'].create([{ + 'production_id': self.id, + 'order_id': new_po.id + }]) + + self.is_po = True + + return po_ids + + def _get_purchase_price(self, product_id): + override_vendor = product_id.x_manufacture.override_vendor_id + query = [('product_id', '=', product_id.id), + ('vendor_id', '=', override_vendor.id)] + purchase_price = self.env['purchase.pricelist'].search(query, limit=1) + if purchase_price: + return self._get_valid_purchase_price(purchase_price) + else: + purchase_price = self.env['purchase.pricelist'].search( + [('product_id', '=', product_id.id), + ('is_winner', '=', True)], + limit=1) + + return self._get_valid_purchase_price(purchase_price) + + def _get_valid_purchase_price(self, purchase_price): + current_time = datetime.now() + delta_time = current_time - timedelta(days=365) + # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') + + price = 0 + taxes = '' + vendor_id = '' + human_last_update = purchase_price.human_last_update or datetime.min + system_last_update = purchase_price.system_last_update or datetime.min + + if purchase_price.taxes_product_id.type_tax_use == 'purchase': + price = purchase_price.product_price + taxes = purchase_price.taxes_product_id.id + vendor_id = purchase_price.vendor_id.id + if delta_time > human_last_update: + price = 0 + taxes = '' + vendor_id = '' + + if system_last_update > human_last_update: + if purchase_price.taxes_system_id.type_tax_use == 'purchase': + price = purchase_price.system_price + taxes = purchase_price.taxes_system_id.id + vendor_id = purchase_price.vendor_id.id + if delta_time > system_last_update: + price = 0 + taxes = '' + vendor_id = '' + + return price, taxes, vendor_id + + +class ProductionPurchaseMatch(models.Model): + _name = 'production.purchase.match' + _order = 'production_id, id' + + production_id = fields.Many2one('mrp.production', string='Ref', required=True, ondelete='cascade', index=True, copy=False) + order_id = fields.Many2one('purchase.order', string='Purchase Order') + vendor = fields.Char(string='Vendor', compute='_compute_info_po') + total = fields.Float(string='Total', compute='_compute_info_po') + + def _compute_info_po(self): + for match in self: + match.vendor = match.order_id.partner_id.name + match.total = match.order_id.amount_total + diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index d90c4a8a..b107f389 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -74,6 +74,7 @@ class PurchaseOrder(models.Model): date_done_picking = fields.Datetime(string='Date Done Picking', compute='get_date_done') bills_dp_id = fields.Many2one('account.move', string='Bills DP') bills_pelunasan_id = fields.Many2one('account.move', string='Bills Pelunasan') + product_bom_id = fields.Many2one('product.product', string='Product Bom') grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery', compute='_compute_grand_total') total_margin_match = fields.Float(string='Total Margin Match', compute='_compute_total_margin_match') approve_by = fields.Many2one('res.users', string='Approve By') @@ -726,9 +727,25 @@ class PurchaseOrder(models.Model): self.unlink_purchasing_job_state() self._check_qty_plafon_product() + if self.product_bom_id: + self._remove_product_bom() return res + def _remove_product_bom(self): + pj = self.env['v.purchasing.job'].search([ + ('product_id', '=', self.product_bom_id.id) + ]) + + if pj: + pj_state = self.env['purchasing.job.state'].search([ + ('purchasing_job_id', '=', pj.id) + ]) + + if pj_state: + pj_state.note = 'Product BOM Sudah Di PO' + pj_state.date_po = datetime.utcnow() + def check_ppn_mix(self): reference_taxes = self.order_line[0].taxes_id diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 902bc34b..ea2f46cb 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -25,6 +25,15 @@ class PurchasingJob(models.Model): ], string='APO?') purchase_representative_id = fields.Many2one('res.users', string="Purchase Representative", readonly=True) note = fields.Char(string="Note Detail") + date_po = fields.Datetime(string='Date PO', copy=False) + + def unlink(self): + # Example: Delete related records from the underlying model + underlying_records = self.env['purchasing.job'].search([ + ('product_id', 'in', self.mapped('product_id').ids) + ]) + underlying_records.unlink() + return super(PurchasingJob, self).unlink() def redirect_to_pjs(self): states = self.env['purchasing.job.state'].search([ @@ -56,8 +65,9 @@ class PurchasingJob(models.Model): pmp.action, max(pjs.status_apo::text) AS status_apo, max(pjs.note::text) AS note, + max(pjs.date_po::text) AS date_po, CASE - WHEN pmp.brand IN ('Tekiro', 'RYU', 'Rexco') THEN 27 + WHEN pmp.brand IN ('Tekiro', 'RYU', 'Rexco', 'RYU (Sparepart)') 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/purchasing_job_state.py b/indoteknik_custom/models/purchasing_job_state.py index 1838a496..d014edfe 100644 --- a/indoteknik_custom/models/purchasing_job_state.py +++ b/indoteknik_custom/models/purchasing_job_state.py @@ -14,4 +14,5 @@ class PurchasingJobState(models.Model): ('not_apo', 'Belum APO'), ('apo', 'APO') ], string='APO?', copy=False) - note = fields.Char(string="Note Detail") + note = fields.Char(string="Note Detail", copy=False) + date_po = fields.Datetime(string='Date PO', copy=False) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c7fcabbb..c83ffd61 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -67,12 +67,14 @@ class ShippingOption(models.Model): class SaleOrder(models.Model): _inherit = "sale.order" + koli_lines = fields.One2many('sales.order.koli', 'sale_order_id', string='Sales Order Koli', auto_join=True) fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2') fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment') reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines') order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True) total_margin = fields.Float('Total Margin', compute='_compute_total_margin', help="Total Margin in Sales Order Header") total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', help="Total % Margin in Sales Order Header") + total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header") approval_status = fields.Selection([ ('pengajuan1', 'Approval Manager'), ('pengajuan2', 'Approval Pimpinan'), @@ -104,6 +106,7 @@ class SaleOrder(models.Model): domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", help="Dipakai untuk alamat tempel", tracking=True) fee_third_party = fields.Float('Fee Pihak Ketiga') + biaya_lain_lain = fields.Float('Biaya Lain Lain') so_status = fields.Selection([ ('terproses', 'Terproses'), ('sebagian', 'Sebagian Diproses'), @@ -234,6 +237,18 @@ class SaleOrder(models.Model): nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") + @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') + def _check_total_margin_excl_third_party(self): + for rec in self: + if rec.fee_third_party == 0 and rec.total_margin_excl_third_party != rec.total_percent_margin: + # Gunakan direct SQL atau flag context untuk menghindari rekursi + self.env.cr.execute(""" + UPDATE sale_order + SET total_margin_excl_third_party = %s + WHERE id = %s + """, (rec.total_percent_margin, rec.id)) + self.invalidate_cache() + @api.constrains('shipping_option_id') def _check_shipping_option(self): for rec in self: @@ -453,7 +468,7 @@ class SaleOrder(models.Model): delivery_amt = order.delivery_amt else: delivery_amt = 0 - order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2) + order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (order.amount_untaxed-delivery_amt-order.fee_third_party-order.biaya_lain_lain)) * 100, 2) def _compute_date_kirim(self): for rec in self: @@ -654,9 +669,10 @@ class SaleOrder(models.Model): if self.email and not re.match(pattern, self.email): raise UserError('Email yang anda input kurang valid') + # @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered') def _validate_delivery_amt(self): if self.delivery_amt < 1: - if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik'): + if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): if(self.carrier_id.id == 1): raise UserError('Untuk Kurir Indoteknik Delivery, Estimasi Ongkos Kirim Harus di isi') else: @@ -1427,7 +1443,7 @@ class SaleOrder(models.Model): delivery_amt = 0 # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2) - order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-order.fee_third_party)) * 100, 2) + order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-order.fee_third_party-order.biaya_lain_lain)) * 100, 2) # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2) @api.onchange('sales_tax_id') @@ -1684,6 +1700,7 @@ class SaleOrder(models.Model): order._compute_etrts_date() order._validate_expected_ready_ship_date() order._validate_delivery_amt() + # order._check_total_margin_excl_third_party() # order._update_partner_details() return order @@ -1726,7 +1743,9 @@ class SaleOrder(models.Model): "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") res = super(SaleOrder, self).write(vals) - self._validate_delivery_amt() + # self._check_total_margin_excl_third_party() + if any(fields in vals for fields in ['delivery_amt', 'carrier_id', 'shipping_cost_covered']): + self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() return res
\ No newline at end of file diff --git a/indoteknik_custom/models/sales_order_koli.py b/indoteknik_custom/models/sales_order_koli.py new file mode 100644 index 00000000..c782a40e --- /dev/null +++ b/indoteknik_custom/models/sales_order_koli.py @@ -0,0 +1,26 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +import logging + +_logger = logging.getLogger(__name__) + + +class SalesOrderKoli(models.Model): + _name = 'sales.order.koli' + _description = 'Sales Order Koli' + _order = 'sale_order_id, id' + _rec_name = 'koli_id' + + sale_order_id = fields.Many2one( + 'sale.order', + string='Sale Order Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + koli_id = fields.Many2one('check.koli', string='Koli') + picking_id = fields.Many2one('stock.picking', string='Picking') + state = fields.Selection([('not_delivered', 'Not Delivered'), ('delivered', 'Delivered')], string='Status', default='not_delivered') + diff --git a/indoteknik_custom/models/stock_backorder_confirmation.py b/indoteknik_custom/models/stock_backorder_confirmation.py new file mode 100644 index 00000000..d8a41f54 --- /dev/null +++ b/indoteknik_custom/models/stock_backorder_confirmation.py @@ -0,0 +1,33 @@ +from odoo import models, fields, api +from odoo.tools.float_utils import float_compare + +class StockBackorderConfirmation(models.TransientModel): + _inherit = 'stock.backorder.confirmation' + + def process(self): + pickings_to_do = self.env['stock.picking'] + pickings_not_to_do = self.env['stock.picking'] + for line in self.backorder_confirmation_line_ids: + line.picking_id.send_mail_bills() + # line.picking_id.send_koli_to_so() + if line.to_backorder is True: + pickings_to_do |= line.picking_id + else: + pickings_not_to_do |= line.picking_id + + for pick_id in pickings_not_to_do: + moves_to_log = {} + for move in pick_id.move_lines: + if float_compare(move.product_uom_qty, + move.quantity_done, + precision_rounding=move.product_uom.rounding) > 0: + moves_to_log[move] = (move.quantity_done, move.product_uom_qty) + pick_id._log_less_quantities_than_expected(moves_to_log) + + pickings_to_validate = self.env.context.get('button_validate_picking_ids') + if pickings_to_validate: + pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate).with_context(skip_backorder=True) + if pickings_not_to_do: + pickings_to_validate = pickings_to_validate.with_context(picking_ids_not_to_backorder=pickings_not_to_do.ids) + return pickings_to_validate.button_validate() + return True diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index 21210619..c2a293f9 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -5,17 +5,20 @@ class StockImmediateTransfer(models.TransientModel): _inherit = 'stock.immediate.transfer' def process(self): - """Override process method to add send_mail_bills logic.""" pickings_to_do = self.env['stock.picking'] pickings_not_to_do = self.env['stock.picking'] for line in self.immediate_transfer_line_ids: + line.picking_id.send_mail_bills() + line.picking_id.send_koli_to_so() if line.to_immediate is True: pickings_to_do |= line.picking_id else: pickings_not_to_do |= line.picking_id for picking in pickings_to_do: + # picking.send_mail_bills() + # picking.send_koli_to_so() # If still in draft => confirm and assign if picking.state == 'draft': picking.action_confirm() @@ -23,6 +26,7 @@ class StockImmediateTransfer(models.TransientModel): picking.action_assign() if picking.state != 'assigned': raise UserError(_("Could not reserve all requested products. Please use the 'Mark as Todo' button to handle the reservation manually.")) + for move in picking.move_lines.filtered(lambda m: m.state not in ['done', 'cancel']): for move_line in move.move_line_ids: move_line.qty_done = move_line.product_uom_qty @@ -32,4 +36,6 @@ class StockImmediateTransfer(models.TransientModel): pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate) pickings_to_validate = pickings_to_validate - pickings_not_to_do return pickings_to_validate.with_context(skip_immediate=True).button_validate() + return True + diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 6b631713..514acad0 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -13,6 +13,21 @@ class StockMove(models.Model): ) qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') barcode = fields.Char(string='Barcode', related='product_id.barcode') + vendor_id = fields.Many2one('res.partner' ,string='Vendor') + + @api.constrains('product_id') + def constrains_product_to_fill_vendor(self): + for rec in self: + if rec.product_id and rec.bom_line_id: + if rec.product_id.x_manufacture.override_vendor_id: + rec.vendor_id = rec.product_id.x_manufacture.override_vendor_id.id + else: + purchase_pricelist = self.env['purchase.pricelist'].search( + [('product_id', '=', rec.product_id.id), + ('is_winner', '=', True)], + limit=1) + if purchase_pricelist: + rec.vendor_id = purchase_pricelist.vendor_id.id def _compute_qr_code_variant(self): for rec in self: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ba7a9452..4a200ac5 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1,6 +1,8 @@ from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools.float_utils import float_is_zero +from collections import defaultdict +from datetime import timedelta, datetime from datetime import timedelta, datetime as waktu from itertools import groupby import pytz, requests, json, requests @@ -24,6 +26,9 @@ _biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1l class StockPicking(models.Model): _inherit = 'stock.picking' _order = 'final_seq ASC' + konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True) + scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True) + check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True) check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True) @@ -71,6 +76,11 @@ class StockPicking(models.Model): readonly=True, copy=False ) + out_code = fields.Integer( + string="Out Code", + readonly=True, + related="id", + ) sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan", ) paket_documentation = fields.Binary(string="Dokumentasi Paket", ) sj_return_date = fields.Datetime(string="SJ Return Date", ) @@ -96,6 +106,7 @@ class StockPicking(models.Model): ('not_paid', 'Customer belum bayar'), ('partial', 'Kirim Parsial'), ('indent', 'Indent'), + ('waiting_schedule', 'Menunggu Jadwal Kirim'), ('self_pickup', 'Barang belum di pickup Customer'), ('expedition_closed', 'Eskpedisi belum buka') ], string='Note Logistic', help='jika field ini diisi maka tidak akan dihitung ke lead time') @@ -141,6 +152,18 @@ class StockPicking(models.Model): # def _compute_show_state_approve_md(self): # for record in self: # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" + quantity_koli = fields.Float(string="Quantity Koli", copy=False) + total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli') + so_lama = fields.Boolean('SO LAMA') + + @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli') + def _compute_total_mapping_koli(self): + for record in self: + total = 0.0 + for line in record.konfirm_koli_lines: + if line.pick_id and line.pick_id.quantity_koli: + total += line.pick_id.quantity_koli + record.total_mapping_koli = total @api.model def _compute_dokumen_tanda_terima(self): @@ -185,12 +208,78 @@ class StockPicking(models.Model): lalamove_image_url = fields.Char(string="Lalamove Image URL") lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html") + total_koli = fields.Integer(compute='_compute_total_koli', string="Total Koli") + total_koli_display = fields.Char(compute='_compute_total_koli_display', string="Total Koli Display") + linked_out_picking_id = fields.Many2one('stock.picking', string="Linked BU/OUT", copy=False) + total_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli") + # Biteship Section biteship_id = fields.Char(string="Biteship Respon ID") biteship_tracking_id = fields.Char(string="Biteship Trackcking ID") biteship_waybill_id = fields.Char(string="Biteship Waybill ID") final_seq = fields.Float(string='Remaining Time') + shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') + state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') + @api.constrains('konfirm_koli_lines') + def _constrains_konfirm_koli_lines(self): + now = datetime.datetime.utcnow() + for picking in self: + if len(picking.konfirm_koli_lines) > 0: + picking.state_packing = 'packing_done' + else: + picking.state_packing = 'not_packing' + + @api.constrains('scan_koli_lines') + def _constrains_scan_koli_lines(self): + now = datetime.datetime.utcnow() + for picking in self: + if len(picking.scan_koli_lines) > 0: + picking.driver_departure_date = now + + @api.depends('total_so_koli') + def _compute_total_so_koli(self): + for picking in self: + if picking.state == 'done': + picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '=', 'delivered')]) + else: + picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')]) + + @api.depends('total_koli') + def _compute_total_koli(self): + for picking in self: + picking.total_koli = self.env['scan.koli'].search_count([('picking_id', '=', picking.id)]) + + @api.depends('total_koli', 'total_so_koli') + def _compute_total_koli_display(self): + for picking in self: + picking.total_koli_display = f"{picking.total_koli} / {picking.total_so_koli}" + + @api.constrains('quantity_koli') + def _constrains_quantity_koli(self): + for picking in self: + if not picking.linked_out_picking_id: + so_koli = self.env['sales.order.koli'].search([('picking_id', '=', picking.id)]) + + if so_koli: + so_koli.unlink() + + for rec in picking.check_koli_lines: + self.env['sales.order.koli'].create({ + 'sale_order_id': picking.sale_id.id, + 'picking_id': picking.id, + 'koli_id': rec.id, + }) + else: + raise UserError('Tidak Bisa Mengubah Quantity Koli Karena Koli Dari Picking Ini Sudah Dipakai Di BU/OUT!') + + @api.onchange('quantity_koli') + def _onchange_quantity_koli(self): + self.check_koli_lines = [(5, 0, 0)] + self.check_koli_lines = [(0, 0, { + 'koli': f"{self.name}/{str(i+1).zfill(3)}", + 'picking_id': self.id, + }) for i in range(int(self.quantity_koli))] def schduled_update_sequance(self): query = "SELECT update_sequance_stock_picking();" @@ -396,7 +485,7 @@ class StockPicking(models.Model): "name": order_line.product_id.name, "description": order_line.name, "value": order_line.price_unit, - "quantity": move_line.qty_done, # Menggunakan qty_done dari move_line + "quantity": move_line.qty_done, "weight": order_line.weight }) @@ -503,36 +592,14 @@ class StockPicking(models.Model): res = super(StockPicking, self).do_unreserve() current_time = datetime.datetime.utcnow() self.date_unreserve = current_time - # self.check_state_reserve() return res - # def check_state_reserve(self): - # do = self.search([ - # ('state', 'not in', ['cancel', 'draft', 'done']), - # ('picking_type_code', '=', 'outgoing') - # ]) - - # for rec in do: - # rec.state_reserve = 'ready' - # rec.date_reserved = datetime.datetime.utcnow() - - # for line in rec.move_ids_without_package: - # if line.product_uom_qty > line.reserved_availability: - # rec.state_reserve = 'waiting' - # rec.date_reserved = '' - # break - def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'outgoing'), - ('name', 'ilike', 'BU/OUT/'), - ]) - - count = self.search_count([ - ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'outgoing') + ('picking_type_code', '=', 'internal'), + ('name', 'ilike', 'BU/PICK/'), ]) for picking in pickings: @@ -552,14 +619,8 @@ class StockPicking(models.Model): def check_state_reserve_backorder(self): pickings = self.search([ ('backorder_id', '!=', False), - ('name', 'ilike', 'BU/OUT/'), - ('picking_type_code', '=', 'outgoing'), - ('state', 'not in', ['cancel', 'draft', 'done']) - ]) - - count = self.search_count([ - ('backorder_id', '!=', False), - ('picking_type_code', '=', 'outgoing'), + ('name', 'ilike', 'BU/PICK/'), + ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) @@ -880,6 +941,35 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' + threshold_datetime = waktu(2025, 4, 11, 6, 26) + + if (len(self.konfirm_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and self.create_date > threshold_datetime + and not self.so_lama): + raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) + + if (len(self.scan_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and self.create_date > threshold_datetime + and not self.so_lama): + raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + + # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + # raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) + + if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: + raise UserError(_("Tidak ada koli! Harap periksa kembali.")) + + if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name: + raise UserError(_("Tidak ada Check Product! Harap periksa kembali.")) + + if self.total_koli > self.total_so_koli: + raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") + % (self.total_koli, self.t1otal_so_koli)) + if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") @@ -927,14 +1017,99 @@ class StockPicking(models.Model): self.validation_minus_onhand_quantity() self.responsible = self.env.user.id + # self.send_koli_to_so() + if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name: + self.check_koli() res = super(StockPicking, self).button_validate() self.calculate_line_no() self.date_done = datetime.datetime.utcnow() + self.driver_departure_date = datetime.datetime.utcnow() self.state_reserve = 'done' self.final_seq = 0 + self.set_picking_code_out() + self.send_koli_to_so() + if not self.env.context.get('skip_koli_check'): + for picking in self: + if picking.sale_id: + all_koli_ids = picking.sale_id.koli_lines.filtered(lambda k: k.state != 'delivered').ids + scanned_koli_ids = picking.scan_koli_lines.mapped('koli_id.id') + + missing_koli_ids = set(all_koli_ids) - set(scanned_koli_ids) + + if len(missing_koli_ids) > 0 and picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: + missing_koli_names = picking.sale_id.koli_lines.filtered(lambda k: k.id in missing_koli_ids and k.state != 'delivered').mapped('display_name') + missing_koli_list = "\n".join(f"- {name}" for name in missing_koli_names) + + # Buat wizard modal warning + wizard = self.env['warning.modal.wizard'].create({ + 'message': f"Berikut Koli yang belum discan:\n{missing_koli_list}", + 'picking_id': picking.id, + }) + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'warning.modal.wizard', + 'view_mode': 'form', + 'res_id': wizard.id, + 'target': 'new', + } self.send_mail_bills() return res + + def set_picking_code_out(self): + for picking in self: + # Check if picking meets criteria + is_bu_pick = picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name + if not is_bu_pick: + continue + + # Find matching outgoing transfers + bu_out_transfers = self.search([ + ('name', 'like', 'BU/OUT/%'), + ('sale_id', '=', picking.sale_id.id), + ('picking_type_code', '=', 'outgoing'), + ('picking_code', '=', False), + ('state', 'not in', ['done', 'cancel']) + ]) + + # Assign sequence code to each matching transfer + for transfer in bu_out_transfers: + transfer.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code') + + def check_koli(self): + for picking in self: + sale_id = picking.sale_id + for koli_lines in picking.scan_koli_lines: + if koli_lines.koli_id.sale_order_id != sale_id: + raise UserError('Koli tidak sesuai') + + def send_koli_to_so(self): + for picking in self: + if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name: + for koli_line in picking.check_koli_lines: + existing_koli = self.env['sales.order.koli'].search([ + ('sale_order_id', '=', picking.sale_id.id), + ('picking_id', '=', picking.id), + ('koli_id', '=', koli_line.id) + ], limit=1) + + if not existing_koli: + self.env['sales.order.koli'].create({ + 'sale_order_id': picking.sale_id.id, + 'picking_id': picking.id, + 'koli_id': koli_line.id + }) + + if picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: + if picking.state == 'done': + for koli_line in picking.scan_koli_lines: + existing_koli = self.env['sales.order.koli'].search([ + ('sale_order_id', '=', picking.sale_id.id), + ('koli_id', '=', koli_line.koli_id.koli_id.id) + ], limit=1) + + existing_koli.state = 'delivered' def check_qty_done_stock(self): for line in self.move_line_ids_without_package: @@ -1015,25 +1190,32 @@ class StockPicking(models.Model): res = super(StockPicking, self).action_cancel() return res - @api.model def create(self, vals): self._use_faktur(vals) - if vals.get('picking_type_code') == 'incoming' and vals.get('location_dest_id') == 58: - if 'name' in vals and vals['name'].startswith('BU/IN/'): - vals['name'] = vals['name'].replace('BU/IN/', 'BU/INPUT/', 1) - - if vals.get('picking_type_code') == 'internal' and vals.get('location_id') == 58: - if 'name' in vals and vals['name'].startswith('BU/INT'): - new_name = vals['name'].replace('BU/INT', 'BU/IN', 1) - # Periksa apakah nama sudah ada - if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', vals.get('company_id'))]) > 0: - new_name = f"{new_name}-DUP" - vals['name'] = new_name - return super(StockPicking, self).create(vals) + records = super(StockPicking, self).create(vals) + + # Panggil sync_sale_line setelah record dibuat + # records.sync_sale_line(vals) + return records + + def sync_sale_line(self, vals): + # Pastikan kita bekerja dengan record yang sudah ada + for picking in self: + if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name: + for line in picking.move_ids_without_package: + if line.product_id and picking.sale_id: + sale_line = self.env['sale.order.line'].search([ + ('product_id', '=', line.product_id.id), + ('order_id', '=', picking.sale_id.id) + ], limit=1) # Tambahkan limit=1 untuk efisiensi + + if sale_line: + line.sale_line_id = sale_line.id def write(self, vals): self._use_faktur(vals) + self.sync_sale_line(vals) for picking in self: # Periksa apakah kondisi terpenuhi saat data diubah if (vals.get('picking_type_code', picking.picking_type_code) == 'incoming' and @@ -1454,4 +1636,245 @@ class BarcodeProduct(models.Model): if record.barcode and not record.product_id.barcode: record.product_id.barcode = record.barcode else: - raise UserError('Barcode sudah terisi')
\ No newline at end of file + raise UserError('Barcode sudah terisi') + +class CheckKoli(models.Model): + _name = 'check.koli' + _description = 'Check Koli' + _order = 'picking_id, id' + _rec_name = 'koli' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + koli = fields.Char(string='Koli') + reserved_id = fields.Many2one('stock.picking', string='Reserved Picking') + check_koli_progress = fields.Char( + string="Progress Check Koli" + ) + + @api.constrains('koli') + def _check_koli_progress(self): + for check in self: + if check.picking_id: + all_checks = self.env['check.koli'].search([('picking_id', '=', check.picking_id.id)], order='id') + if all_checks: + check_index = list(all_checks).index(check) + 1 # Nomor urut check + total_so_koli = len(all_checks) + check.check_koli_progress = f"{check_index}/{total_so_koli}" if total_so_koli else "0/0" + +class ScanKoli(models.Model): + _name = 'scan.koli' + _description = 'Scan Koli' + _order = 'picking_id, id' + _rec_name = 'koli_id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + koli_id = fields.Many2one('sales.order.koli', string='Koli') + scan_koli_progress = fields.Char( + string="Progress Scan Koli", + compute="_compute_scan_koli_progress" + ) + + def _compute_scan_koli_progress(self): + for scan in self: + if scan.picking_id: + all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') + if all_scans: + scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan + total_so_koli = scan.picking_id.total_so_koli + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" + + @api.onchange('koli_id') + def _onchange_koli_compare_with_konfirm_koli(self): + if not self.koli_id: + return + + if not self.picking_id.konfirm_koli_lines: + raise UserError(_('Mapping Koli Harus Diisi!')) + + koli_picking = self.koli_id.picking_id._origin + + konfirm_pick_ids = [ + line.pick_id._origin + for line in self.picking_id.konfirm_koli_lines + if line.pick_id + ] + + if koli_picking not in konfirm_pick_ids: + raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) + + @api.constrains('picking_id', 'koli_id') + def _check_duplicate_koli(self): + for record in self: + if record.koli_id: + existing_koli = self.search([ + ('picking_id', '=', record.picking_id.id), + ('koli_id', '=', record.koli_id.id), + ('id', '!=', record.id) + ]) + if existing_koli: + raise ValidationError(f"⚠️ Koli '{record.koli_id.display_name}' sudah discan untuk picking ini!") + + def unlink(self): + picking_ids = set(self.mapped('koli_id.picking_id.id')) + for scan in self: + koli = scan.koli_id.koli_id + if koli: + koli.reserved_id = False + + for picking_id in picking_ids: + remaining_scans = self.env['sales.order.koli'].search_count([ + ('koli_id.picking_id', '=', picking_id) + ]) + + delete_koli = len(self.filtered(lambda rec: rec.koli_id.picking_id.id == picking_id)) + + if remaining_scans == delete_koli: + picking = self.env['stock.picking'].browse(picking_id) + picking.linked_out_picking_id = False + else: + raise UserError(_("Tidak dapat menghapus scan koli, karena masih ada scan koli lain yang tersisa untuk picking ini.")) + + for picking_id in picking_ids: + self._reset_qty_done_if_no_scan(picking_id) + + # self.check_koli_not_balance() + + return super(ScanKoli, self).unlink() + + @api.onchange('koli_id','scan_koli_progress') + def onchange_koli_id(self): + if not self.koli_id: + return + + for scan in self: + if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: + scan.koli_id.koli_id.reserved_id = scan.picking_id.id.origin + scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin + + def _compute_scan_koli_progress(self): + for scan in self: + if scan.picking_id: + all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') + if all_scans: + scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan + total_so_koli = scan.picking_id.total_so_koli + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" + + @api.constrains('picking_id', 'picking_id.total_so_koli') + def _check_koli_validation(self): + for scan in self.picking_id.scan_koli_lines: + scan.koli_id.koli_id.reserved_id = scan.picking_id.id + scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id + + total_scans = len(self.picking_id.scan_koli_lines) + if total_scans != self.picking_id.total_so_koli: + raise UserError(_("Jumlah scan koli tidak sama dengan total SO koli!")) + + # def check_koli_not_balance(self): + # for scan in self: + # total_scancs = self.env['scan.koli'].search_count([('picking_id', '=', scan.picking_id.id), ('id', '!=', scan.id)]) + # if total_scancs != scan.picking_id.total_so_koli: + # raise UserError(_("Jumlah scan koli tidak sama dengan total SO koli!")) + + @api.onchange('koli_id') + def _onchange_koli_id(self): + if not self.koli_id: + return + + source_koli_so = self.picking_id.group_id.id + source_koli = self.koli_id.picking_id.group_id.id + + if source_koli_so != source_koli: + raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + + @api.constrains('koli_id') + def _send_product_from_koli_id(self): + if not self.koli_id: + return + + koli_count_by_picking = defaultdict(int) + for scan in self: + koli_count_by_picking[scan.koli_id.picking_id.id] += 1 + + for picking_id, total_koli in koli_count_by_picking.items(): + picking = self.env['stock.picking'].browse(picking_id) + + if total_koli == picking.quantity_koli: + pick_moves = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) + out_moves = self.env['stock.move.line'].search([('picking_id', '=', picking.linked_out_picking_id.id)]) + + for pick_move in pick_moves: + corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) + if corresponding_out_move: + corresponding_out_move.qty_done += pick_move.qty_done + + def _reset_qty_done_if_no_scan(self, picking_id): + product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) + + for move in product_bu_pick: + product_bu_out = self.env['stock.move.line'].search([('picking_id', '=', self.picking_id.id), ('product_id', '=', move.product_id.id)]) + for bu_out in product_bu_out: + bu_out.qty_done -= move.qty_done + # if remaining_scans == 0: + # picking = self.env['stock.picking'].browse(picking_id) + # picking.move_line_ids_without_package.write({'qty_done': 0}) + # picking.message_post(body=f"⚠ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.") + + # return remaining_scans + +class KonfirmKoli(models.Model): + _name = 'konfirm.koli' + _description = 'Konfirm Koli' + _order = 'picking_id, id' + _rec_name = 'pick_id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + pick_id = fields.Many2one('stock.picking', string='Pick') + + @api.constrains('pick_id') + def _check_duplicate_pick_id(self): + for rec in self: + exist = self.search([ + ('pick_id', '=', rec.pick_id.id), + ('picking_id', '=', rec.picking_id.id), + ('id', '!=', rec.id), + ]) + + if exist: + raise UserError(f"⚠️ '{rec.pick_id.display_name}' sudah discan untuk picking ini!") + +class WarningModalWizard(models.TransientModel): + _name = 'warning.modal.wizard' + _description = 'Peringatan Koli Belum Diperiksa' + + name = fields.Char(default="⚠️ Perhatian!") + message = fields.Text() + picking_id = fields.Many2one('stock.picking') + + def action_continue(self): + if self.picking_id: + return self.picking_id.with_context(skip_koli_check=True).button_validate() + return {'type': 'ir.actions.act_window_close'} + + diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index d4347235..a683d80e 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -24,4 +24,15 @@ class ReturnPicking(models.TransientModel): # if not stock_picking.approval_return_status == 'approved' and purchase.invoice_ids: # raise UserError('Harus Approval Accounting AP untuk melakukan Retur') - return res
\ No newline at end of file + return res + +class ReturnPickingLine(models.TransientModel): + _inherit = 'stock.return.picking.line' + + @api.onchange('quantity') + def _onchange_quantity(self): + for rec in self: + qty_done = rec.move_id.quantity_done + + if rec.quantity > qty_done: + raise UserError(f"Quantity yang Anda masukkan tidak boleh melebihi quantity done yaitu: {qty_done}")
\ No newline at end of file diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 58562487..4d164bcb 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -153,9 +153,16 @@ access_va_multi_approve,access.va.multi.approve,model_va_multi_approve,,1,1,1,1 access_va_multi_reject,access.va.multi.reject,model_va_multi_reject,,1,1,1,1 access_vendor_sla,access.vendor_sla,model_vendor_sla,,1,1,1,1 access_check_product,access.check.product,model_check_product,,1,1,1,1 +access_check_koli,access.check.koli,model_check_koli,,1,1,1,1 +access_scan_koli,access.scan.koli,model_scan_koli,,1,1,1,1 +access_konfirm_koli,access.konfirm.koli,model_konfirm_koli,,1,1,1,1 access_stock_immediate_transfer,access.stock.immediate.transfer,model_stock_immediate_transfer,,1,1,1,1 access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1 access_purchase_order_unlock_wizard,access.purchase.order.unlock.wizard,model_purchase_order_unlock_wizard,,1,1,1,1 +access_sales_order_koli,access.sales.order.koli,model_sales_order_koli,,1,1,1,1 +access_stock_backorder_confirmation,access.stock.backorder.confirmation,model_stock_backorder_confirmation,,1,1,1,1 +access_warning_modal_wizard,access.warning.modal.wizard,model_warning_modal_wizard,,1,1,1,1 + access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pengajuan_tempo_line,,1,1,1,1 access_user_pengajuan_tempo,access.user.pengajuan.tempo,model_user_pengajuan_tempo,,1,1,1,1 access_reject_reason_wizard,reject.reason.wizard,model_reject_reason_wizard,,1,1,1,0 @@ -167,5 +174,7 @@ access_barcoding_product_line,access.barcoding.product.line,model_barcoding_prod access_account_payment_register,access.account.payment.register,model_account_payment_register,,1,1,1,1 access_stock_inventory,access.stock.inventory,model_stock_inventory,,1,1,1,1 access_cancel_reason_order,cancel.reason.order,model_cancel_reason_order,,1,1,1,0 +access_reject_reason_commision,reject.reason.commision,model_reject_reason_commision,,1,1,1,0 access_shipping_option,shipping.option,model_shipping_option,,1,1,1,1 access_sale_order_delay,sale.order.delay,model_sale_order_delay,,1,1,1,1 +access_production_purchase_match,access.production.purchase.match,model_production_purchase_match,,1,1,1,1 diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index bb1628bc..9f0e1e8a 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -11,14 +11,14 @@ <field name="partner_ids" widget="many2many_tags"/> <field name="commision_percent"/> <field name="commision_amt" readonly="1"/> - <field name="status" readonly="1"/> + <field name="status" readonly="1" decoration-success="status == 'approved'" widget="badge" optional="show"/> <field name="payment_status" readonly="1" decoration-success="payment_status == 'payment'" decoration-danger="payment_status == 'pending'" widget="badge"/> <field name="brand_ids" widget="many2many_tags"/> - <field name="grouped_so_number" readonly="1"/> - <field name="grouped_invoice_number" readonly="1"/> + <field name="grouped_so_number" readonly="1" optional="hide"/> + <field name="grouped_invoice_number" readonly="1" optional="hide"/> </tree> </field> </record> @@ -30,10 +30,12 @@ <tree editable="top" create="false"> <field name="partner_id" readonly="1"/> <field name="invoice_id" readonly="1"/> + <field name="sale_order_id" readonly="1"/> <field name="state" readonly="1"/> <field name="product_id" readonly="1" optional="hide"/> <field name="dpp" readonly="1"/> <field name="total_percent_margin" readonly="1"/> + <field name="total_margin_excl_third_party" readonly="1"/> <field name="tax" readonly="1" optional="hide"/> <field name="total" readonly="1" optional="hide"/> </tree> @@ -45,14 +47,27 @@ <field name="model">customer.commision</field> <field name="arch" type="xml"> <form> +<!-- attrs="{'invisible': [('status', 'in', ['draft','pengajuan1','pengajuan2','pengajuan3','pengajuan4'])]}"--> <header> - <button name="action_confirm_customer_commision" + <button name="action_confirm_customer_commision" string="Confirm" type="object" + attrs="{'invisible': [('status', 'in', ['approved','reject'])]}" options="{}"/> + <button name="action_reject" + string="Reject" + attrs="{'invisible': [('status', 'in', ['approved','reject'])]}" + type="object"/> + <button name="button_draft" + string="Reset to Draft" + attrs="{'invisible': [('status', '!=', 'reject')]}" + type="object"/> <button name="action_confirm_customer_payment" string="Konfirmasi Pembayaran" type="object" options="{}" attrs="{'invisible': [('payment_status', '==', 'payment')], 'readonly': [('payment_status', '=', 'payment')]}"/> + <field name="status" widget="statusbar" + statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,pengajuan4,approved" + statusbar_colors='{"reject":"red"}'/> </header> <sheet string="Customer Commision"> <div class="oe_button_box" name="button_box"/> @@ -64,8 +79,10 @@ <field name="description"/> <field name="commision_percent"/> <field name="commision_amt"/> + <field name="commision_amt_text"/> <field name="grouped_so_number" readonly="1"/> <field name="grouped_invoice_number" readonly="1"/> + <field name="approved_by" readonly="1"/> </group> <group> <div> @@ -76,10 +93,11 @@ /> </div> <field name="date_to"/> + <field name="sales_id"/> <field name="commision_type"/> <field name="brand_ids" widget="many2many_tags"/> <field name="notification" readonly="1"/> - <field name="status" readonly="1"/> +<!-- <field name="status" readonly="1"/>--> <field name="payment_status" readonly="1" /> <field name="total_dpp"/> </group> @@ -96,6 +114,11 @@ <field name="note_transfer"/> </group> </page> + <page string="Finance Notes"> + <group> + <field name="note_finnance"/> + </group> + </page> </notebook> </sheet> <div class="oe_chatter"> @@ -106,6 +129,30 @@ </field> </record> + <!-- Wizard for Reject Reason --> + <record id="view_reject_reason_wizard_form" model="ir.ui.view"> + <field name="name">reject.reason.commision.form</field> + <field name="model">reject.reason.commision</field> + <field name="arch" type="xml"> + <form string="Reject Reason"> + <group> + <field name="reason_reject" widget="text"/> + </group> + <footer> + <button string="Confirm" type="object" name="confirm_reject" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> + + <record id="action_reject_reason_wizard" model="ir.actions.act_window"> + <field name="name">Reject Reason</field> + <field name="res_model">reject.reason.commision</field> + <field name="view_mode">form</field> + <field name="target">new</field> + </record> + <record id="view_customer_commision_filter" model="ir.ui.view"> <field name="name">customer.commision.list.select</field> <field name="model">customer.commision</field> diff --git a/indoteknik_custom/views/mrp_production.xml b/indoteknik_custom/views/mrp_production.xml index 95f419f6..f8278f39 100644 --- a/indoteknik_custom/views/mrp_production.xml +++ b/indoteknik_custom/views/mrp_production.xml @@ -5,10 +5,22 @@ <field name="model">mrp.production</field> <field name="inherit_id" ref="mrp.mrp_production_form_view" /> <field name="arch" type="xml"> + <button name="button_mark_done" position="after"> + <button name="create_po_from_manufacturing" type="object" string="Create PO" class="oe_highlight" attrs="{'invisible': ['|', ('state', '!=', 'confirmed'), ('is_po', '=', True)]}"/> + </button> <field name="bom_id" position="after"> <field name="desc"/> <field name="sale_order"/> + <field name="is_po"/> </field> + <xpath expr="//form/sheet/notebook/page/field[@name='move_raw_ids']/tree/field[@name='product_uom_qty']" position="before"> + <field name="vendor_id"/> + </xpath> + <xpath expr="//form/sheet/notebook/page[@name='miscellaneous']" position="after"> + <page string="Purchase Match" name="purchase_order_lines_indent"> + <field name="production_purchase_match"/> + </page> + </xpath> </field> </record> @@ -23,4 +35,16 @@ </field> </field> </record> + + <record id="production_purchase_match_tree" model="ir.ui.view"> + <field name="name">production.purchase.match.tree</field> + <field name="model">production.purchase.match</field> + <field name="arch" type="xml"> + <tree> + <field name="order_id" readonly="1"/> + <field name="vendor" readonly="1"/> + <field name="total" readonly="1"/> + </tree> + </field> + </record> </odoo> diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index 36c0db13..d6ad2408 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -65,6 +65,7 @@ <field name="payment_term_id"/> <field name="total_cost_service" attrs="{'required': [('partner_id', 'in', [9688, 29712])]}"/> <field name="total_delivery_amt" attrs="{'required': [('partner_id', 'in', [9688, 29712])]}"/> + <field name="product_bom_id"/> </field> <field name="amount_total" position="after"> <field name="total_margin"/> diff --git a/indoteknik_custom/views/purchasing_job.xml b/indoteknik_custom/views/purchasing_job.xml index 16f1bedd..bb1c7643 100644 --- a/indoteknik_custom/views/purchasing_job.xml +++ b/indoteknik_custom/views/purchasing_job.xml @@ -17,6 +17,7 @@ <field name="status_apo" invisible="1"/> <field name="action"/> <field name="note"/> + <field name="date_po"/> </tree> </field> </record> diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 0d190f37..2c64181e 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -31,7 +31,9 @@ <field name="shipping_paid_by" attrs="{'required': ['|', ('create_date', '>', '2023-06-15'), ('create_date', '=', False)]}"/> <field name="delivery_amt"/> <field name="fee_third_party"/> + <field name="biaya_lain_lain"/> <field name="total_percent_margin"/> + <field name="total_margin_excl_third_party" readonly="1"/> <field name="type_promotion"/> <label for="voucher_id"/> <div class="o_row"> @@ -284,6 +286,9 @@ <page string="Reject Line" name="page_sale_order_reject_line"> <field name="reject_line" readonly="1"/> </page> + <page string="Koli" name="page_sales_order_koli_line"> + <field name="koli_lines" readonly="1"/> + </page> </page> </field> </record> @@ -422,6 +427,20 @@ </data> <data> + <record id="sales_order_koli_tree" model="ir.ui.view"> + <field name="name">sales.order.koli.tree</field> + <field name="model">sales.order.koli</field> + <field name="arch" type="xml"> + <tree editable="top" create="false" delete="false"> + <field name="koli_id" readonly="1"/> + <field name="picking_id" readonly="1"/> + <field name="state" readonly="1"/> + </tree> + </field> + </record> + </data> + + <data> </data> <record id="sales_order_fulfillment_v2_tree" model="ir.ui.view"> diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 72fdefa7..4c60a496 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -19,8 +19,9 @@ <field name="note" optional="hide"/> <field name="date_reserved" optional="hide"/> <field name="state_reserve" optional="hide"/> + <field name="state_packing" widget="badge" decoration-success="state_packing == 'packing_done'" decoration-danger="state_packing == 'not_packing'" optional="hide"/> <field name="final_seq"/> - <field name="state_approve_md" widget="badge" decoration-success="state_approve_md == 'done'" decoration-warning="state_approve_md == 'pending'" /> + <field name="state_approve_md" widget="badge" decoration-success="state_approve_md == 'done'" decoration-warning="state_approve_md == 'pending'" optional="hide"/> <!-- <field name="countdown_hours" optional="hide"/> <field name="countdown_ready_to_ship" /> --> </field> @@ -84,10 +85,15 @@ /> </button> <field name="backorder_id" position="after"> + <field name="shipping_method_so_id"/> <field name="summary_qty_detail"/> <field name="count_line_detail"/> <field name="dokumen_tanda_terima"/> <field name="dokumen_pengiriman"/> + <field name="quantity_koli" attrs="{'invisible': [('location_dest_id', '!=', 60)], 'required': [('location_dest_id', '=', 60)]}"/> + <field name="total_mapping_koli" attrs="{'invisible': [('location_id', '!=', 60)]}"/> + <field name="total_koli_display" readonly="1" attrs="{'invisible': [('location_id', '!=', 60)]}"/> + <field name="linked_out_picking_id" readonly="1" attrs="{'invisible': [('location_id', '=', 60)]}"/> </field> <field name="weight_uom_name" position="after"> <group> @@ -142,6 +148,7 @@ <field name="approval_status"/> <field name="approval_receipt_status"/> <field name="approval_return_status"/> + <field name="so_lama"/> </field> <field name="product_id" position="before"> <field name="line_no" attrs="{'readonly': 1}" optional="hide"/> @@ -162,13 +169,14 @@ </group> </group> </page> - <page string="Delivery" name="delivery_order"> + <page string="Delivery" name="delivery_order" attrs="{'invisible': [('location_dest_id', '=', 60)]}"> <group> <group> <field name="notee"/> <field name="note_logistic"/> <field name="responsible" /> <field name="carrier_id"/> + <field name="out_code" attrs="{'invisible': [['out_code', '=', False]]}"/> <field name="picking_code" attrs="{'invisible': [['picking_code', '=', False]]}"/> <field name="picking_code" string="Picking code (akan digenerate ketika sudah di-validate)" attrs="{'invisible': [['picking_code', '!=', False]]}"/> <field name="driver_departure_date" attrs="{'readonly':[('invoice_status', '=', 'invoiced')]}"/> @@ -210,25 +218,67 @@ </group> </group> </page> - <page string="Check Product" name="check_product"> + <page string="Check Product" name="check_product" attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}"> <field name="check_product_lines"/> </page> <page string="Barcode Product" name="barcode_product" attrs="{'invisible': [('picking_type_code', '!=', 'incoming')]}"> <field name="barcode_product_lines"/> </page> + <page string="Check Koli" name="check_koli" attrs="{'invisible': [('location_dest_id', '!=', 60)]}"> + <field name="check_koli_lines"/> + </page> + <page string="Mapping Koli" name="konfirm_koli" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}"> + <field name="konfirm_koli_lines"/> + </page> + <page string="Konfirm Koli" name="scan_koli" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}"> + <field name="scan_koli_lines"/> + </page> </page> </field> </record> + <record id="scan_koli_tree" model="ir.ui.view"> + <field name="name">scan.koli.tree</field> + <field name="model">scan.koli</field> + <field name="arch" type="xml"> + <tree editable="bottom"> + <field name="koli_id" options="{'no_create': True}" required="1" domain="[('state', '=', 'not_delivered')]"/> + <field name="scan_koli_progress"/> + </tree> + </field> + </record> + + <record id="konfirm_koli_tree" model="ir.ui.view"> + <field name="name">konfirm.koli.tree</field> + <field name="model">konfirm.koli</field> + <field name="arch" type="xml"> + <tree editable="bottom"> + <field name="pick_id" options="{'no_create': True}" required="1" domain="[('picking_type_code', '=', 'internal'), ('group_id', '=', parent.group_id)]"/> + </tree> + </field> + </record> + + <record id="check_koli_tree" model="ir.ui.view"> + <field name="name">check.koli.tree</field> + <field name="model">check.koli</field> + <field name="arch" type="xml"> + <tree editable="bottom"> + <field name="koli"/> + <field name="reserved_id"/> + <field name="check_koli_progress"/> + </tree> + </field> + </record> + <record id="check_product_tree" model="ir.ui.view"> <field name="name">check.product.tree</field> <field name="model">check.product</field> <field name="arch" type="xml"> <tree editable="bottom" decoration-warning="status == 'Pending'" decoration-success="status == 'Done'"> - <field name="product_id"/> - <field name="quantity"/> - <field name="status"/> + <field name="product_id" required="1" options="{'no_create': True}"/> + <field name="quantity" readonly="1"/> + <field name="status" readonly="1"/> </tree> </field> </record> @@ -265,6 +315,35 @@ <field name="purchase_representative_id"/> </field> </field> - </record> + </record> + + <record id="view_warning_modal_wizard_form" model="ir.ui.view"> + <field name="name">warning.modal.wizard.form</field> + <field name="model">warning.modal.wizard</field> + <field name="arch" type="xml"> + <form string="Peringatan Koli Belum Diperiksa"> + <sheet> + <div class="oe_title"> + <h2><span>⚠️ Perhatian!</span></h2> + </div> + <group> + <field name="message" readonly="1" nolabel="1" widget="text"/> + </group> + </sheet> + <footer> + <button name="action_continue" type="object" string="Lanjutkan" class="btn-primary"/> + <button string="Tutup" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> + + <record id="action_warning_modal_wizard" model="ir.actions.act_window"> + <field name="name">Peringatan Koli</field> + <field name="res_model">warning.modal.wizard</field> + <field name="view_mode">form</field> + <field name="view_id" ref="view_warning_modal_wizard_form"/> + <field name="target">new</field> + </record> </data> </odoo>
\ No newline at end of file |
