diff options
| author | trisusilo48 <tri.susilo@altama.co.id> | 2025-03-19 10:23:27 +0700 |
|---|---|---|
| committer | trisusilo48 <tri.susilo@altama.co.id> | 2025-03-19 10:23:27 +0700 |
| commit | e0ba42d70cda5ab419badac0775b550e63645b3b (patch) | |
| tree | d58923caf57b341a3c3c97f1fa08278e641788a8 | |
| parent | fc5defa647bcdd317dc2d4069432c2dcc1141344 (diff) | |
| parent | 9e293f201e3c43662c3866fa06eaa36805d69cc5 (diff) | |
Merge branch 'odoo-backup' into feature/feedback_bitehisp
# Conflicts:
# indoteknik_custom/models/sale_order.py
| -rw-r--r-- | indoteknik_api/controllers/api_v1/partner.py | 2 | ||||
| -rw-r--r-- | indoteknik_api/controllers/api_v1/user.py | 20 | ||||
| -rw-r--r-- | indoteknik_custom/models/commision.py | 15 | ||||
| -rw-r--r-- | indoteknik_custom/models/invoice_reklas.py | 6 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 80 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_picking.py | 45 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 1 | ||||
| -rw-r--r-- | indoteknik_custom/views/customer_commision.xml | 4 | ||||
| -rwxr-xr-x | indoteknik_custom/views/sale_order.xml | 1 | ||||
| -rw-r--r-- | indoteknik_custom/views/stock_picking.xml | 16 |
10 files changed, 163 insertions, 27 deletions
diff --git a/indoteknik_api/controllers/api_v1/partner.py b/indoteknik_api/controllers/api_v1/partner.py index ebd91210..126fded4 100644 --- a/indoteknik_api/controllers/api_v1/partner.py +++ b/indoteknik_api/controllers/api_v1/partner.py @@ -78,7 +78,7 @@ class Partner(controller.Controller): 'district_id': ['number', 'alias:kecamatan_id'], 'sub_district_id': ['number', 'alias:kelurahan_id', 'exclude_if_null'], 'zip': ['required'], - 'longitude': '', # Perbaikan dari longtitude ke longitude + 'longtitude': '', 'latitude': '', 'address_map': [], 'alamat_lengkap_text': [] diff --git a/indoteknik_api/controllers/api_v1/user.py b/indoteknik_api/controllers/api_v1/user.py index c0974367..b5b7e055 100644 --- a/indoteknik_api/controllers/api_v1/user.py +++ b/indoteknik_api/controllers/api_v1/user.py @@ -131,6 +131,7 @@ class User(controller.Controller): nama_wajib_pajak = kw.get('nama_wajib_pajak', False) is_pkp = kw.get('is_pkp') is_terdaftar = kw.get('is_terdaftar', False) + is_terdaftar = False if is_terdaftar == 'false' else is_terdaftar type_acc = kw.get('type_acc', 'individu') or 'individu' if not name or not email or not password: @@ -162,9 +163,7 @@ class User(controller.Controller): 'sel_groups_1_9_10': 9 } - user = request.env['res.users'].create(user_data) - user.partner_id.email = email - user.partner_id.mobile = phone + if type_acc == 'business' and business_name: # Eksekusi query SQL menggunakan Levenshtein distance @@ -182,7 +181,9 @@ class User(controller.Controller): if result and is_terdaftar: match_company_name = result[2] match_company_id = result[0] - + user = request.env['res.users'].create(user_data) + user.partner_id.email = email + user.partner_id.mobile = phone # Create a user company request request.env['user.company.request'].create({ 'user_id': user.partner_id.id, @@ -190,6 +191,9 @@ class User(controller.Controller): 'user_input': business_name }) else: + if not result and is_terdaftar: + response['reason'] = 'BISNIS_NOT_FOUND' + return self.response(response) if not nama_wajib_pajak and is_pkp == 'false': nama_wajib_pajak = business_name @@ -213,6 +217,10 @@ class User(controller.Controller): 'property_account_payable_id': 438, 'active': False, } + + user = request.env['res.users'].create(user_data) + user.partner_id.email = email + user.partner_id.mobile = phone new_company = request.env['res.partner'].create(new_company_data) request.env['user.company.request'].create({ 'user_id': user.partner_id.id, @@ -247,8 +255,10 @@ class User(controller.Controller): 'mimetype': sppkp_mimetype }) new_company.message_post(body="SPPKP Uploaded", attachment_ids=[sppkp_attachment.id]) - if type_acc == 'individu': + user = request.env['res.users'].create(user_data) + user.partner_id.email = email + user.partner_id.mobile = phone user.partner_id.customer_type = 'nonpkp' user.partner_id.npwp = '00.000.000.0-000.000' user.partner_id.sppkp = '-' diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 6920154a..0d31e954 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -157,7 +157,22 @@ class CustomerCommision(models.Model): ('pending', 'Pending'), ('payment', 'Payment'), ], string='Payment Status', copy=False, readonly=True, tracking=3, default='pending') + 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') + 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.sale_id: + so_numbers.add(line.invoice_id.sale_id.name) + invoice_numbers.add(line.invoice_id.name) + + rec.grouped_so_number = ', '.join(sorted(so_numbers)) + rec.grouped_invoice_number = ', '.join(sorted(invoice_numbers)) # add status for type of commision, fee, rebate / cashback # include child or not? diff --git a/indoteknik_custom/models/invoice_reklas.py b/indoteknik_custom/models/invoice_reklas.py index f5bb5a25..d10d4c31 100644 --- a/indoteknik_custom/models/invoice_reklas.py +++ b/indoteknik_custom/models/invoice_reklas.py @@ -18,6 +18,12 @@ class InvoiceReklas(models.TransientModel): ('pembelian', 'Pembelian'), ], string='Reklas Tipe') + @api.onchange('reklas_type') + def _onchange_reklas_type(self): + if self.reklas_type == 'penjualan': + invoices = self.env['account.move'].browse(self._context.get('active_ids', [])) + self.pay_amt = invoices.amount_total + def create_reklas(self): if not self.reklas_type: raise UserError('Reklas Tipe harus diisi') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index e2755eba..acad7729 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1,3 +1,5 @@ +from re import search + from odoo import fields, models, api, _ from odoo.exceptions import UserError, ValidationError from datetime import datetime, timedelta @@ -51,6 +53,16 @@ class CancelReasonOrder(models.TransientModel): order.confirm_cancel_order() return {'type': 'ir.actions.act_window_close'} + +class ShippingOption(models.Model): + _name = "shipping.option" + _description = "Shipping Option" + + name = fields.Char(string="Option Name", required=True) + price = fields.Float(string="Price", required=True) + provider = fields.Char(string="Provider") + etd = fields.Char(string="Estimated Delivery Time") + sale_order_id = fields.Many2one('sale.order', string="Sale Order", ondelete="cascade") class SaleOrder(models.Model): _inherit = "sale.order" @@ -196,8 +208,7 @@ class SaleOrder(models.Model): # ) expected_ready_to_ship = fields.Datetime( string='ET Ready to Ship', - copy=False, - store=True + copy=False ) shipping_method_picking = fields.Char(string='Shipping Method Picking', compute='_compute_shipping_method_picking') @@ -221,6 +232,12 @@ class SaleOrder(models.Model): string="Attachment Bukti Cancel", readonly=False, ) 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('shipping_option_id') + def _check_shipping_option(self): + for rec in self: + rec.delivery_amt = rec.shipping_option_id.price def _compute_shipping_method_picking(self): for order in self: @@ -268,15 +285,26 @@ class SaleOrder(models.Model): if total_weight == 0: raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") - + if total_weight < 10: total_weight = 10 + self.delivery_amt = total_weight * 3000 + + shipping_option = self.env["shipping.option"].create({ + "name": "Indoteknik Delivery", + "price": self.delivery_amt, + "provider": "Indoteknik", + "etd": "1-2 Hari", + "sale_order_id": self.id, + }) + self.shipping_option_id = shipping_option.id def action_estimate_shipping(self): if self.carrier_id.id in [1, 151]: self.action_indoteknik_estimate_shipping() return + total_weight = 0 missing_weight_products = [] @@ -294,33 +322,35 @@ class SaleOrder(models.Model): if total_weight == 0: raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") - # Mendapatkan city_id berdasarkan nama kota - origin_city_name = self.warehouse_id.partner_id.kota_id.name destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id - if not destination_subsdistrict_id: - raise UserError("Gagal mendapatkan ID kota asal atau tujuan.") + raise UserError("Gagal mendapatkan ID kota tujuan.") result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id) if result: - estimated_cost = result['rajaongkir']['results'][0]['costs'][0]['cost'][0]['value'] - self.delivery_amt = estimated_cost - - shipping_info = [] + shipping_options = [] for courier in result['rajaongkir']['results']: for cost_detail in courier['costs']: service = cost_detail['service'] description = cost_detail['description'] etd = cost_detail['cost'][0]['etd'] value = cost_detail['cost'][0]['value'] - shipping_info.append(f"Service: {service}, Description: {description}, ETD: {etd} hari, Cost: Rp {value}") + shipping_options.append((service, description, etd, value, courier['code'])) + + self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() - log_message = "<br/>".join(shipping_info) + for service, description, etd, value, provider in shipping_options: + self.env["shipping.option"].create({ + "name": service, + "price": value, + "provider": provider, + "etd": etd, + "sale_order_id": self.id, + }) + + self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id - description_ongkir = result['rajaongkir']['results'][0]['costs'][0]['description'] - etd_ongkir = result['rajaongkir']['results'][0]['costs'][0]['cost'][0]['etd'] - service_ongkir = result['rajaongkir']['results'][0]['costs'][0]['service'] - self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Service: {service_ongkir}<br/>Description: {description_ongkir}<br/>ETD: {etd_ongkir}<br/>Detail Lain:<br/>{log_message}") + self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}") else: raise UserError("Gagal mendapatkan estimasi ongkir.") @@ -948,8 +978,20 @@ class SaleOrder(models.Model): raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") if not order.user_id.active: raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact") - + + def check_product_bom(self): + for order in self: + for line in order.order_line: + if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False: + search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)],order='name desc') + if search_bom: + confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') + if not confirmed_bom: + raise UserError("Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") + else: + raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD") def sale_order_approve(self): + self.check_product_bom() self.check_credit_limit() self.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: @@ -1009,6 +1051,7 @@ class SaleOrder(models.Model): order.approval_status = 'pengajuan2' return self._create_approval_notification('Pimpinan') elif order._requires_approval_margin_manager(): + self.check_product_bom() self.check_credit_limit() self.check_limit_so_to_invoice() order.approval_status = 'pengajuan1' @@ -1188,6 +1231,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: + order.check_product_bom() order.check_credit_limit() order.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index edc9cc78..9e5fca66 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -131,6 +131,16 @@ class StockPicking(models.Model): ('cancel', 'Cancelled'), ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note") + state_approve_md = fields.Selection([ + ('waiting', 'Waiting For Approve by MD'), + ('pending', 'Pending (perlu koordinasi dengan MD)'), + ('done', 'Approve by MD'), + ], string='Approval MD Gudang Selisih', tracking=True, copy=False, help="The current state of the MD Approval transfer barang from gudang selisih.") + show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") + + 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" @api.model def _compute_dokumen_tanda_terima(self): @@ -891,8 +901,36 @@ class StockPicking(models.Model): qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) if line.qty_done > qty_onhand: raise UserError('Quantity Done melebihi Quantity Onhand') + def button_state_approve_md(self): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.env.user.id in users_in_group.mapped('id'): + self.state_approve_md = 'done' + else: + raise UserError('Hanya MD yang bisa Approve') + + def button_state_pending_md(self): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.env.user.id in users_in_group.mapped('id'): + self.state_approve_md = 'pending' + else: + raise UserError('Hanya MD yang bisa Approve') def button_validate(self): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.location_id.id == 47 and self.env.user.id not in users_in_group.mapped('id') and self.state_approve_md != 'done': + self.state_approve_md = 'waiting' if self.state_approve_md != 'pending' else 'pending' + self.env.cr.commit() + raise UserError("Transfer dari gudang selisih harus di approve MD, Hubungi MD agar bisa di Validate") + else: + if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): + self.state_approve_md = 'done' + 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") @@ -917,7 +955,7 @@ class StockPicking(models.Model): if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: raise UserError("Harus di Approve oleh Logistik") - + if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager: raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara") @@ -1018,9 +1056,12 @@ class StockPicking(models.Model): return True def action_cancel(self): - if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': + if not self.env.user.is_logistic_approver: if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") + + if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic'): + raise UserError("Button ini hanya untuk Logistik") res = super(StockPicking, self).action_cancel() return res diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 4d9d8cf7..4d0e51eb 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -167,3 +167,4 @@ 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_shipping_option,shipping.option,model_shipping_option,,1,1,1,1 diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index 51172b1c..bb1628bc 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -17,6 +17,8 @@ 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"/> </tree> </field> </record> @@ -62,6 +64,8 @@ <field name="description"/> <field name="commision_percent"/> <field name="commision_amt"/> + <field name="grouped_so_number" readonly="1"/> + <field name="grouped_invoice_number" readonly="1"/> </group> <group> <div> diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index ebee64b1..0d190f37 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -100,6 +100,7 @@ <field name="sales_tax_id" domain="[('type_tax_use','=','sale'), ('active', '=', True)]" required="1"/> <field name="carrier_id" required="1"/> <field name="delivery_service_type" readonly="1"/> + <field name="shipping_option_id"/> </field> <field name="medium_id" position="after"> <field name="date_doc_kirim" readonly="1"/> diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index dadd5021..ce07888a 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -19,7 +19,8 @@ <field name="note" optional="hide"/> <field name="date_reserved" optional="hide"/> <field name="state_reserve" optional="hide"/> - <field name="final_seq"/> + <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="countdown_hours" optional="hide"/> <field name="countdown_ready_to_ship" /> --> </field> @@ -70,6 +71,17 @@ type="object" attrs="{'invisible': [('carrier_id', '!=', 9)]}" /> +<!-- <button name="button_state_approve_md"--> +<!-- string="Approve MD Gudang Selisih"--> +<!-- type="object"--> +<!-- attrs="{'invisible': [('state_approve_md', 'not in', ['waiting', 'pending'])]}"--> +<!-- />--> + +<!-- <button name="button_state_pending_md"--> +<!-- string="Pending MD Gudang Selisih"--> +<!-- type="object"--> +<!-- attrs="{'invisible': [('state_approve_md', 'not in', ['waiting'])]}"--> +<!-- />--> </button> <field name="backorder_id" position="after"> <field name="summary_qty_detail"/> @@ -97,6 +109,8 @@ <field name="arrival_time"/> </field> <field name="origin" position="after"> +<!-- <field name="show_state_approve_md" invisible="1" optional="hide"/>--> +<!-- <field name="state_approve_md" widget="badge" attrs="{'invisible': [('show_state_approve_md', '=', False)]}"/>--> <field name="purchase_id"/> <field name="sale_order"/> <field name="invoice_status"/> |
