From 01507fad2a05124137541dc187a06631732c606e Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 11 Dec 2024 15:14:31 +0700 Subject: master vendor sla --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/vendor_sla.py | 26 ++++++++++++++++ indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/vendor_sla.xml | 42 ++++++++++++++++++++++++++ indoteknik_custom/views/x_banner_category.xml | 2 +- 6 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 indoteknik_custom/models/vendor_sla.py create mode 100644 indoteknik_custom/views/vendor_sla.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 89f62524..5f77d2de 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -155,6 +155,7 @@ 'report/report_invoice.xml', 'report/report_picking.xml', 'report/report_sale_order.xml', + 'views/vendor_sla.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index ad6d75dd..70a84bd0 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -134,3 +134,4 @@ from . import find_page from . import approval_retur_picking from . import va_multi_approve from . import va_multi_reject +from . import vendor_sla diff --git a/indoteknik_custom/models/vendor_sla.py b/indoteknik_custom/models/vendor_sla.py new file mode 100644 index 00000000..1e0a344f --- /dev/null +++ b/indoteknik_custom/models/vendor_sla.py @@ -0,0 +1,26 @@ +from odoo import models, fields, api + +class VendorSLA(models.Model): + _name = 'vendor.sla' + _description = 'Vendor SLA' + _rec_name = 'id_vendor' + + id_vendor = fields.Many2one('res.partner', string='Name') + duration = fields.Integer(string='Duration', description='Duration SLA') + unit = fields.Selection( + [('jam', 'Jam'),('hari', 'Hari')], + string="Unit" + ) + duration_unit = fields.Char(string="Duration (Unit)", compute="_compute_duration_unit") + + + @api.depends('duration', 'unit') + def _compute_duration_unit(self): + for record in self: + if record.duration and record.unit: + record.duration_unit = f"{record.duration} {record.unit}" + else: + record.duration_unit = "" + + + \ 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 2375df9d..dcfee14b 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -148,3 +148,4 @@ access_sales_order_fulfillment_v2,access.sales.order.fulfillment.v2,model_sales_ access_v_move_outstanding,access.v.move.outstanding,model_v_move_outstanding,,1,1,1,1 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 diff --git a/indoteknik_custom/views/vendor_sla.xml b/indoteknik_custom/views/vendor_sla.xml new file mode 100644 index 00000000..d0e7f3e6 --- /dev/null +++ b/indoteknik_custom/views/vendor_sla.xml @@ -0,0 +1,42 @@ + + + + Vendor SLA + vendor.sla + tree,form + + + + Vendor SLA + vendor.sla + + + + + + + + + + Vendor SLA + vendor.sla + +
+ + + + + + + +
+
+
+ + +
\ No newline at end of file diff --git a/indoteknik_custom/views/x_banner_category.xml b/indoteknik_custom/views/x_banner_category.xml index 11feb207..a83c4129 100755 --- a/indoteknik_custom/views/x_banner_category.xml +++ b/indoteknik_custom/views/x_banner_category.xml @@ -23,7 +23,7 @@ - + -- cgit v1.2.3 From 3ba967de0aa6c70b0d070a7ca723d49008aa0dd2 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Fri, 13 Dec 2024 14:15:17 +0700 Subject: product sla --- indoteknik_custom/models/product_sla.py | 15 +++++++++++++++ indoteknik_custom/models/vendor_sla.py | 6 +++--- indoteknik_custom/views/product_sla.xml | 2 ++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/product_sla.py b/indoteknik_custom/models/product_sla.py index 2e663d30..05210cdc 100644 --- a/indoteknik_custom/models/product_sla.py +++ b/indoteknik_custom/models/product_sla.py @@ -12,6 +12,8 @@ class ProductSla(models.Model): _rec_name = 'product_variant_id' product_variant_id = fields.Many2one('product.product',string='Product') + vendor_id = fields.Many2one('res.partner',string='Vendor', readonly=True) + sla_vendor = fields.Char(string='SLA Vendor', readonly=True) avg_leadtime = fields.Char(string='AVG Leadtime', readonly=True) leadtime = fields.Char(string='Leadtime', readonly=True) sla = fields.Char(string='SLA', readonly=True) @@ -36,6 +38,7 @@ class ProductSla(models.Model): product.sla_version += 1 product_sla = self.search([('product_variant_id', '=', product.id)], limit=1) + print(product_sla.id, product.id) if not product_sla: product_sla = self.env['product.sla'].create({ 'product_variant_id': product.id, @@ -48,6 +51,18 @@ class ProductSla(models.Model): self.sla = '-' product = self.product_variant_id + + q_vendor = [ + ('product_id', '=', product.id), + ('is_winner', '=', True) + ] + + vendor = self.env['purchase.pricelist'].search(q_vendor) + print(vendor.vendor_id.id) + if vendor: + vendor_sla = self.env['vendor.sla'].search([('id_vendor', '=', vendor.vendor_id.id)], limit=1) + self.sla_vendor = "{} {}".format(vendor_sla.duration, vendor_sla.unit) + self.vendor_id = vendor.vendor_id.id qty_available = 0 qty_available = product.qty_onhand_bandengan diff --git a/indoteknik_custom/models/vendor_sla.py b/indoteknik_custom/models/vendor_sla.py index 1e0a344f..9af86a14 100644 --- a/indoteknik_custom/models/vendor_sla.py +++ b/indoteknik_custom/models/vendor_sla.py @@ -5,11 +5,11 @@ class VendorSLA(models.Model): _description = 'Vendor SLA' _rec_name = 'id_vendor' - id_vendor = fields.Many2one('res.partner', string='Name') - duration = fields.Integer(string='Duration', description='Duration SLA') + id_vendor = fields.Many2one('res.partner', string='Name', domain="[('industry_id', '=', 46)]") + duration = fields.Integer(string='Duration', description='SLA Duration') unit = fields.Selection( [('jam', 'Jam'),('hari', 'Hari')], - string="Unit" + string="SLA Time" ) duration_unit = fields.Char(string="Duration (Unit)", compute="_compute_duration_unit") diff --git a/indoteknik_custom/views/product_sla.xml b/indoteknik_custom/views/product_sla.xml index 8b0e874b..5276bb03 100644 --- a/indoteknik_custom/views/product_sla.xml +++ b/indoteknik_custom/views/product_sla.xml @@ -6,6 +6,8 @@ + + -- cgit v1.2.3 From 60f54931f8eac477ab737abab1710789e0a2aaf4 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 30 Dec 2024 16:30:20 +0700 Subject: vendor sla --- indoteknik_custom/models/product_sla.py | 30 ++++++++++++++++++++++-------- indoteknik_custom/views/vendor_sla.xml | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/indoteknik_custom/models/product_sla.py b/indoteknik_custom/models/product_sla.py index 05210cdc..f597ec44 100644 --- a/indoteknik_custom/models/product_sla.py +++ b/indoteknik_custom/models/product_sla.py @@ -25,6 +25,7 @@ class ProductSla(models.Model): def generate_product_variant_id_sla(self, limit=5000): # Filter produk non-Altama + products = self.env['product.product'].search([ ('x_manufacture', 'not in', [10, 122, 89]), ('location_id', '=', 57), @@ -58,10 +59,19 @@ class ProductSla(models.Model): ] vendor = self.env['purchase.pricelist'].search(q_vendor) + + vendor_duration = 0 print(vendor.vendor_id.id) if vendor: vendor_sla = self.env['vendor.sla'].search([('id_vendor', '=', vendor.vendor_id.id)], limit=1) - self.sla_vendor = "{} {}".format(vendor_sla.duration, vendor_sla.unit) + sla_vendor = int(vendor_sla.duration) if vendor_sla else 0 + if sla_vendor > 0: + if vendor_sla.unit == 'hari': + vendor_duration = vendor_sla.duration * 24 * 60 + else : + vendor_duration = vendor_sla.duration + + self.sla_vendor = vendor_sla.duration_unit self.vendor_id = vendor.vendor_id.id qty_available = 0 @@ -90,10 +100,14 @@ class ProductSla(models.Model): if len(leadtimes) > 0: avg_leadtime = sum(leadtimes) / len(leadtimes) rounded_leadtime = math.ceil(avg_leadtime) - self.avg_leadtime = rounded_leadtime - if rounded_leadtime >= 1 and rounded_leadtime <= 5: - self.sla = '3-7 Hari' - elif rounded_leadtime >= 6 and rounded_leadtime <= 10: - self.sla = '4-12 Hari' - elif rounded_leadtime >= 11: - self.sla = 'Indent' \ No newline at end of file + estimation_sla = (rounded_leadtime * 24 * 60) + vendor_duration + estimation_sla_days = estimation_sla / (24 * 60) + self.sla = estimation_sla_days + self.avg_leadtime = int(rounded_leadtime) + # self.sla = (sla_vendor + self.avg_leadtime) / 2 + # if rounded_leadtime >= 1 and rounded_leadtime <= 5: + # self.sla = '3-7 Hari' + # elif rounded_leadtime >= 6 and rounded_leadtime <= 10: + # self.sla = '4-12 Hari' + # elif rounded_leadtime >= 11: + # self.sla = 'Indent' \ No newline at end of file diff --git a/indoteknik_custom/views/vendor_sla.xml b/indoteknik_custom/views/vendor_sla.xml index d0e7f3e6..cf4425eb 100644 --- a/indoteknik_custom/views/vendor_sla.xml +++ b/indoteknik_custom/views/vendor_sla.xml @@ -26,7 +26,7 @@ - + -- cgit v1.2.3 From 7ac434ec0fcf75cb6eefe1892118b7c18b3db53a Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Tue, 7 Jan 2025 17:02:06 +0700 Subject: sla --- indoteknik_api/controllers/api_v1/product.py | 23 ++++++++++----- indoteknik_custom/models/product_sla.py | 44 ++++++++++++---------------- indoteknik_custom/views/product_sla.xml | 4 +-- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py index 32362582..2573d7a8 100644 --- a/indoteknik_api/controllers/api_v1/product.py +++ b/indoteknik_api/controllers/api_v1/product.py @@ -35,7 +35,7 @@ class Product(controller.Controller): return self.response(categories, headers=[('Cache-Control', 'max-age=3600, public')]) @http.route(prefix + 'product_variant//stock', auth='public', methods=['GET', 'OPTIONS']) - @controller.Controller.must_authorized() + @controller.Controller.must_authorized() def get_product_template_stock_by_id(self, **kw): id = int(kw.get('id')) date_7_days_ago = datetime.now() - timedelta(days=7) @@ -47,12 +47,19 @@ class Product(controller.Controller): ('product_variant_id', '=', id), ('write_date', '>=', date_7_days_ago.strftime("%Y-%m-%d %H:%M:%S")) ], limit=1) + + include_instant = False qty_available = product.qty_free_bandengan - - if qty_available < 0: - qty_available = 0 - + + + if qty_available > 0 : + include_instant = True + else : + qty_available = 0 + if product_sla.sla_vendor_id.unit == 'jam': + include_instant = True + qty = 0 sla_date = '-' @@ -74,9 +81,10 @@ class Product(controller.Controller): if qty_available > 0: qty = qty_available + total_adem + total_excell + sla_date = product_sla.sla or 1 elif qty_altama > 0 or qty_vendor > 0: qty = total_adem if qty_altama > 0 else total_excell - sla_date = '2-4 Hari' + sla_date = product.sla else: sla_date = '3-7 Hari' except: @@ -84,7 +92,7 @@ class Product(controller.Controller): else: if qty_available > 0: qty = qty_available - sla_date = product_sla.sla or '-' + sla_date = product_sla.sla or 'Indent' elif qty_vendor > 0: qty = total_excell sla_date = '2-4 Hari' @@ -92,6 +100,7 @@ class Product(controller.Controller): data = { 'qty': qty, 'sla_date': sla_date, + 'can_instant': include_instant } return self.response(data, headers=[('Cache-Control', 'max-age=600, private')]) diff --git a/indoteknik_custom/models/product_sla.py b/indoteknik_custom/models/product_sla.py index f597ec44..dfdf7662 100644 --- a/indoteknik_custom/models/product_sla.py +++ b/indoteknik_custom/models/product_sla.py @@ -12,8 +12,8 @@ class ProductSla(models.Model): _rec_name = 'product_variant_id' product_variant_id = fields.Many2one('product.product',string='Product') - vendor_id = fields.Many2one('res.partner',string='Vendor', readonly=True) - sla_vendor = fields.Char(string='SLA Vendor', readonly=True) + sla_vendor_id = fields.Many2one('vendor.sla',string='Vendor', readonly=True) + sla_vendor_duration = fields.Char(string='AVG Leadtime', related='sla_vendor_id.duration_unit') avg_leadtime = fields.Char(string='AVG Leadtime', readonly=True) leadtime = fields.Char(string='Leadtime', readonly=True) sla = fields.Char(string='SLA', readonly=True) @@ -52,7 +52,14 @@ class ProductSla(models.Model): self.sla = '-' product = self.product_variant_id + + # qty_available = 0 + # qty_available = product.qty_onhand_bandengan + + # if qty_available > 0: + # self.sla = 1 + q_vendor = [ ('product_id', '=', product.id), ('is_winner', '=', True) @@ -61,7 +68,6 @@ class ProductSla(models.Model): vendor = self.env['purchase.pricelist'].search(q_vendor) vendor_duration = 0 - print(vendor.vendor_id.id) if vendor: vendor_sla = self.env['vendor.sla'].search([('id_vendor', '=', vendor.vendor_id.id)], limit=1) sla_vendor = int(vendor_sla.duration) if vendor_sla else 0 @@ -69,17 +75,9 @@ class ProductSla(models.Model): if vendor_sla.unit == 'hari': vendor_duration = vendor_sla.duration * 24 * 60 else : - vendor_duration = vendor_sla.duration - - self.sla_vendor = vendor_sla.duration_unit - self.vendor_id = vendor.vendor_id.id - - qty_available = 0 - qty_available = product.qty_onhand_bandengan - - - if qty_available > 0: - self.sla = '1 Hari' + vendor_duration = vendor_sla.duration * 60 + + self.sla_vendor_id = vendor_sla.id if vendor_sla else False query = [ ('product_id', '=', product.id), @@ -88,12 +86,13 @@ class ProductSla(models.Model): ('picking_id.state', 'not in', ['cancel']) ] picking = self.env['stock.move.line'].search(query) + leadtimes=[] for stock in picking: date_delivered = stock.picking_id.driver_departure_date - date_so_confirmed = stock.picking_id.sale_id.date_order - if date_delivered and date_so_confirmed: - leadtime = date_delivered - date_so_confirmed + date_do_ready = stock.picking_id.date_reserved + if date_delivered and date_do_ready: + leadtime = date_delivered - date_do_ready leadtime_in_days = leadtime.days leadtimes.append(leadtime_in_days) @@ -102,12 +101,5 @@ class ProductSla(models.Model): rounded_leadtime = math.ceil(avg_leadtime) estimation_sla = (rounded_leadtime * 24 * 60) + vendor_duration estimation_sla_days = estimation_sla / (24 * 60) - self.sla = estimation_sla_days - self.avg_leadtime = int(rounded_leadtime) - # self.sla = (sla_vendor + self.avg_leadtime) / 2 - # if rounded_leadtime >= 1 and rounded_leadtime <= 5: - # self.sla = '3-7 Hari' - # elif rounded_leadtime >= 6 and rounded_leadtime <= 10: - # self.sla = '4-12 Hari' - # elif rounded_leadtime >= 11: - # self.sla = 'Indent' \ No newline at end of file + self.sla = math.ceil(estimation_sla_days) + self.avg_leadtime = int(rounded_leadtime) \ No newline at end of file diff --git a/indoteknik_custom/views/product_sla.xml b/indoteknik_custom/views/product_sla.xml index 5276bb03..3722ef3d 100644 --- a/indoteknik_custom/views/product_sla.xml +++ b/indoteknik_custom/views/product_sla.xml @@ -6,8 +6,8 @@ - - + + -- cgit v1.2.3 From 63878bd84a6eb9094e702963d7c78fcd8dfa1808 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Fri, 10 Jan 2025 10:44:59 +0700 Subject: api sla --- indoteknik_api/controllers/api_v1/product.py | 59 ++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py index 2573d7a8..7570015f 100644 --- a/indoteknik_api/controllers/api_v1/product.py +++ b/indoteknik_api/controllers/api_v1/product.py @@ -1,6 +1,6 @@ from .. import controller from odoo import http -from odoo.http import request +from odoo.http import request, Response from datetime import datetime, timedelta import ast import logging @@ -33,7 +33,58 @@ class Product(controller.Controller): categories.reverse() return self.response(categories, headers=[('Cache-Control', 'max-age=3600, public')]) - + + @http.route(prefix + 'product/variants/sla', auth='none', type='json', csrf=False, cors='*', methods=['GET', 'OPTIONS']) + @controller.Controller.must_authorized() + def get_product_template_sla_by_id(self, **kw): + json_raw = json.loads(request.httprequest.data) + + ids = json_raw.get('ids') + ids = list(map(int, ids)) + + if not ids or not isinstance(ids, list): + return ({'status' : 'Failed','message': 'Parameter "ids" harus berupa list dan tidak boleh kosong.'}) + + sla_days = 0 + products = request.env['product.product'].search([('id', 'in', ids)]) + if len(products) < 1: + return ({ + 'status' : 'Failed', + 'message' : 'Produk Tidak Di Temukan.' + }) + + product_slas = request.env['product.sla'].search([('product_variant_id', 'in', ids)]) + if len(product_slas) < 1: + return ({ + 'status' : 'Failed', + 'message' : 'Produk Tidak Di Temukan.' + }) + + # Mapping SLA untuk mempermudah lookup + sla_map = {sla.product_variant_id.id: sla for sla in product_slas} + + for product in products: + product_sla = sla_map.get(product.id) + if product_sla: + sla_days = max(sla_days, product_sla.sla_vendor_id.duration) + if product.qty_free_bandengan < 1 : + if product_sla.sla_vendor_id.unit != 'jam': + return ({ + 'status' : 'Success', + 'data' : [{ + 'include_instant': False, + 'sla_days': sla_days + }], + }) + # Jika semua loop selesai tanpa include_instant menjadi False + return ({ + 'status' : 'Success', + 'data' : [{ + 'include_instant': True, + 'sla_days': sla_days + }], + }) + @http.route(prefix + 'product_variant//stock', auth='public', methods=['GET', 'OPTIONS']) @controller.Controller.must_authorized() def get_product_template_stock_by_id(self, **kw): @@ -84,9 +135,9 @@ class Product(controller.Controller): sla_date = product_sla.sla or 1 elif qty_altama > 0 or qty_vendor > 0: qty = total_adem if qty_altama > 0 else total_excell - sla_date = product.sla + sla_date = product_sla.sla else: - sla_date = '3-7 Hari' + sla_date = product_sla.sla except: print('error') else: -- cgit v1.2.3 From 3d672f12eecd77af9a805cebd8ce3a7083957a1f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 16 Jan 2025 10:30:17 +0700 Subject: wms push --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/stock_move.py | 4 + indoteknik_custom/models/stock_picking.py | 234 ++++++++++++++++----- indoteknik_custom/security/ir.model.access.csv | 2 + .../views/stock_backorder_confirmation_views.xml | 14 ++ indoteknik_custom/views/stock_picking.xml | 23 +- 6 files changed, 226 insertions(+), 52 deletions(-) create mode 100644 indoteknik_custom/views/stock_backorder_confirmation_views.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index d44f85db..1ffe9419 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -150,6 +150,7 @@ 'views/form_vendor_approval_multi_approve.xml', 'views/form_vendor_approval_multi_reject.xml', 'views/user_pengajuan_tempo.xml', + 'views/stock_backorder_confirmation_views.xml', 'report/report.xml', 'report/report_banner_banner.xml', 'report/report_banner_banner2.xml', diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index e1d4e74c..6b631713 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -12,9 +12,13 @@ class StockMove(models.Model): default=lambda self: self.product_id.print_barcode, ) qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') + barcode = fields.Char(string='Barcode', related='product_id.barcode') def _compute_qr_code_variant(self): for rec in self: + if rec.picking_id.picking_type_code == 'outgoing' and rec.picking_id and rec.picking_id.origin and rec.picking_id.origin.startswith('SO/'): + rec.qr_code_variant = rec.product_id.qr_code_variant + rec.print_barcode = True if rec.print_barcode and rec.print_barcode == True and rec.product_id and rec.product_id.qr_code_variant: rec.qr_code_variant = rec.product_id.qr_code_variant else: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cd330aeb..05a7ee4a 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -16,7 +16,8 @@ _logger = logging.getLogger(__name__) class StockPicking(models.Model): _inherit = 'stock.picking' - # check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', 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) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') account_id = fields.Many2one('account.account', string='Account') efaktur_id = fields.Many2one('vit.efaktur', string='Faktur Pajak') @@ -176,7 +177,8 @@ class StockPicking(models.Model): pickings = self.env['stock.picking'].search([ ('picking_type_code', '=', 'outgoing'), ('state', '=', 'done'), - ('carrier_id', '=', 9) + ('carrier_id', '=', 9), + ('lalamove_order_id', '!=', False) ]) for picking in pickings: try: @@ -810,7 +812,24 @@ class StockPicking(models.Model): self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' return res - + + + def check_qty_done_stock(self): + for line in self.move_line_ids_without_package: + def check_qty_per_inventory(self, product, location): + quant = self.env['stock.quant'].search([ + ('product_id', '=', product.id), + ('location_id', '=', location.id), + ]) + + if quant: + return quant.quantity + + return 0 + + 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 send_mail_bills(self): if self.picking_type_code == 'incoming' and self.purchase_id: @@ -1008,48 +1027,167 @@ class StockPicking(models.Model): return f'{formatted_fastest_eta} - {formatted_longest_eta}' -# class CheckProduct(models.Model): -# _name = 'check.product' -# _description = 'Check Product' -# _order = 'picking_id, id' - -# picking_id = fields.Many2one('stock.picking', string='Picking Reference', required=True, ondelete='cascade', index=True, copy=False) -# product_id = fields.Many2one('product.product', string='Product') - - -# @api.constrains('product_id') -# def check_product_validity(self): -# """ -# Validate if the product exists in the related stock.picking's move_ids_without_package -# and ensure that the product's quantity does not exceed the available product_uom_qty. -# """ -# for record in self: -# if not record.picking_id or not record.product_id: -# continue - -# # Filter move lines in the related picking for the selected product -# moves = record.picking_id.move_ids_without_package.filtered( -# lambda move: move.product_id.id == record.product_id.id -# ) - -# if not moves: -# raise UserError(( -# "The product '%s' is not available in the related stock picking's moves. " -# "Please check and try again." -# ) % record.product_id.display_name) - -# # Calculate the total entries for the product in check.product for the same picking -# product_entries_count = self.search_count([ -# ('picking_id', '=', record.picking_id.id), -# ('product_id', '=', record.product_id.id) -# ]) - -# # Sum the product_uom_qty for all relevant moves -# total_qty_in_moves = sum(moves.mapped('product_uom_qty')) - -# # Compare the count of entries against the available quantity -# if product_entries_count > total_qty_in_moves: -# raise UserError(( -# "The product '%s' exceeds the allowable quantity (%s) in the related stock picking's moves. " -# "You can only add it %s times." -# ) % (record.product_id.display_name, total_qty_in_moves, total_qty_in_moves)) +class CheckProduct(models.Model): + _name = 'check.product' + _description = 'Check Product' + _order = 'picking_id, id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + product_id = fields.Many2one('product.product', string='Product', required=True) + quantity = fields.Float(string='Quantity', default=1.0, required=True) + status = fields.Char(string='Status', compute='_compute_status') + + @api.depends('quantity') + def _compute_status(self): + for record in self: + moves = record.picking_id.move_ids_without_package.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + if record.quantity < total_qty_in_moves: + record.status = 'Pending' + else: + record.status = 'Done' + + + def create(self, vals): + # Create the record + record = super(CheckProduct, self).create(vals) + # Ensure uniqueness after creation + if not self.env.context.get('skip_consolidate'): + record.with_context(skip_consolidate=True)._consolidate_duplicate_lines() + return record + + def write(self, vals): + # Write changes to the record + result = super(CheckProduct, self).write(vals) + # Ensure uniqueness after writing + if not self.env.context.get('skip_consolidate'): + self.with_context(skip_consolidate=True)._consolidate_duplicate_lines() + return result + + def _sync_check_product_to_moves(self, picking): + """ + Sinkronisasi quantity_done di move_ids_without_package + dengan total quantity dari check.product berdasarkan product_id. + """ + for product_id in picking.check_product_lines.mapped('product_id'): + # Totalkan quantity dari semua baris check.product untuk product_id ini + total_quantity = sum( + line.quantity for line in picking.check_product_lines.filtered(lambda line: line.product_id == product_id) + ) + # Update quantity_done di move yang relevan + moves = picking.move_ids_without_package.filtered(lambda move: move.product_id == product_id) + for move in moves: + move.quantity_done = total_quantity + + def _consolidate_duplicate_lines(self): + """ + Consolidate duplicate lines with the same product_id under the same picking_id + and sync the total quantity to related moves. + """ + for picking in self.mapped('picking_id'): + lines_to_remove = self.env['check.product'] # Recordset untuk menyimpan baris yang akan dihapus + product_lines = picking.check_product_lines.filtered(lambda line: line.product_id) + + # Group lines by product_id + product_groups = {} + for line in product_lines: + product_groups.setdefault(line.product_id.id, []).append(line) + + for product_id, lines in product_groups.items(): + if len(lines) > 1: + # Consolidate duplicate lines + first_line = lines[0] + total_quantity = sum(line.quantity for line in lines) + + # Update the first line's quantity + first_line.with_context(skip_consolidate=True).write({'quantity': total_quantity}) + + # Add the remaining lines to the lines_to_remove recordset + lines_to_remove |= self.env['check.product'].browse([line.id for line in lines[1:]]) + + # Perform unlink after consolidation + if lines_to_remove: + lines_to_remove.unlink() + + # Sync total quantities to moves + self._sync_check_product_to_moves(picking) + + @api.onchange('product_id', 'quantity') + def check_product_validity(self): + for record in self: + if not record.picking_id or not record.product_id: + continue + + # Filter moves related to the selected product + moves = record.picking_id.move_ids_without_package.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + + if not moves: + raise UserError(( + "The product '%s' is not available in the related stock picking's moves. " + "Please check and try again." + ) % record.product_id.display_name) + + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + # Find existing lines for the same product, excluding the current line + existing_lines = record.picking_id.check_product_lines.filtered( + lambda line: line.product_id == record.product_id and line.id != record.id + ) + + if existing_lines: + # Get the first existing line + first_line = existing_lines[0] + + # Calculate the total quantity after addition + total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity + + if total_quantity > total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' sudah melebihi quantity demand: (%s)." + ) % (record.product_id.display_name)) + + else: + # Check if the quantity exceeds the allowed total + if record.quantity > total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' sudah melebihi quantity demand: (%s)." + ) % (record.product_id.display_name)) + + # Set the quantity to the entered value + record.quantity = record.quantity + +class BarcodeProduct(models.Model): + _name = 'barcode.product' + _description = 'Barcode Product' + _order = 'picking_id, id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + product_id = fields.Many2one('product.product', string='Product', required=True) + barcode = fields.Char(string='Barcode') + + @api.constrains('barcode') + def send_barcode_to_product(self): + for record in self: + 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 diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 4e1ecd7d..a26bce31 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -149,6 +149,7 @@ access_sales_order_fulfillment_v2,access.sales.order.fulfillment.v2,model_sales_ access_v_move_outstanding,access.v.move.outstanding,model_v_move_outstanding,,1,1,1,1 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_check_product,access.check.product,model_check_product,,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 @@ -157,3 +158,4 @@ access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pen 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 access_confirm_approval_wizard,confirm.approval.wizard,model_confirm_approval_wizard,,1,1,1,0 +access_barcode_product,access.barcode.product,model_barcode_product,,1,1,1,1 \ No newline at end of file diff --git a/indoteknik_custom/views/stock_backorder_confirmation_views.xml b/indoteknik_custom/views/stock_backorder_confirmation_views.xml new file mode 100644 index 00000000..a622899e --- /dev/null +++ b/indoteknik_custom/views/stock_backorder_confirmation_views.xml @@ -0,0 +1,14 @@ + + + stock_backorder_confirmation_inherit + stock.backorder.confirmation + + + +

+ Ada product yang quantity done nya kurang dari quantity demand. +

+
+
+
+
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 8acba608..1c6ae8f7 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -132,6 +132,7 @@ + @@ -191,15 +192,18 @@ - + + + + - + + + + barcode.product.tree + barcode.product + + + + + + + stock.move.line.operations.tree.inherit -- cgit v1.2.3 From 6424192cdcb34ff2ea57a19b5ee41bf4bf68870c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 16 Jan 2025 13:29:35 +0700 Subject: push --- indoteknik_custom/models/product_template.py | 2 +- indoteknik_custom/models/stock_picking.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 5bedae13..29608297 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -416,7 +416,7 @@ class ProductProduct(models.Model): box_size=5, border=4, ) - qr.add_data(rec.default_code) + qr.add_data(rec.barcode if rec.barcode else rec.default_code) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white") diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 05a7ee4a..cf5b0502 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -894,10 +894,20 @@ class StockPicking(models.Model): @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) return super(StockPicking, self).create(vals) def write(self, vals): self._use_faktur(vals) + for picking in self: + if (vals.get('picking_type_code', picking.picking_type_code) == 'incoming' and + vals.get('location_dest_id', picking.location_dest_id.id) == 58): + if 'name' in vals or picking.name.startswith('BU/IN/'): + name_to_modify = vals.get('name', picking.name) + if name_to_modify.startswith('BU/IN/'): + vals['name'] = name_to_modify.replace('BU/IN/', 'BU/INPUT/', 1) return super(StockPicking, self).write(vals) def _use_faktur(self, vals): -- cgit v1.2.3 From 11a561355208a403d635b16d6c306cc9f19eb714 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 16 Jan 2025 15:51:33 +0700 Subject: change name --- indoteknik_custom/models/stock_picking.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cf5b0502..4c19cb3a 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -897,17 +897,36 @@ class StockPicking(models.Model): 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) def write(self, vals): self._use_faktur(vals) for picking in self: + # Periksa apakah kondisi terpenuhi saat data diubah if (vals.get('picking_type_code', picking.picking_type_code) == 'incoming' and vals.get('location_dest_id', picking.location_dest_id.id) == 58): if 'name' in vals or picking.name.startswith('BU/IN/'): name_to_modify = vals.get('name', picking.name) if name_to_modify.startswith('BU/IN/'): vals['name'] = name_to_modify.replace('BU/IN/', 'BU/INPUT/', 1) + + if (vals.get('picking_type_code', picking.picking_type_code) == 'internal' and + vals.get('location_id', picking.location_id.id) == 58): + name_to_modify = vals.get('name', picking.name) + if name_to_modify.startswith('BU/INT'): + new_name = name_to_modify.replace('BU/INT', 'BU/IN', 1) + # Periksa apakah nama sudah ada + if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0: + new_name = f"{new_name}-DUP" + vals['name'] = new_name return super(StockPicking, self).write(vals) def _use_faktur(self, vals): -- cgit v1.2.3 From e3e7f29ad939a774878316e46e10a5c1370fda77 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 16 Jan 2025 15:53:36 +0700 Subject: sla --- indoteknik_custom/models/product_sla.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/product_sla.py b/indoteknik_custom/models/product_sla.py index dfdf7662..988aa78f 100644 --- a/indoteknik_custom/models/product_sla.py +++ b/indoteknik_custom/models/product_sla.py @@ -99,7 +99,7 @@ class ProductSla(models.Model): if len(leadtimes) > 0: avg_leadtime = sum(leadtimes) / len(leadtimes) rounded_leadtime = math.ceil(avg_leadtime) - estimation_sla = (rounded_leadtime * 24 * 60) + vendor_duration + estimation_sla = ((rounded_leadtime * 24 * 60) + vendor_duration)/2 estimation_sla_days = estimation_sla / (24 * 60) self.sla = math.ceil(estimation_sla_days) self.avg_leadtime = int(rounded_leadtime) \ No newline at end of file -- cgit v1.2.3 From e072cbf06a86cb376d71c0490f0f48c6de1fd3f7 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 21 Jan 2025 14:41:19 +0700 Subject: fix pj --- indoteknik_custom/models/purchasing_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 74b5134e..4efb0cd4 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -182,7 +182,7 @@ class OutstandingSales(models.Model): join product_product pp on pp.id = sm.product_id join product_template pt on pt.id = pp.product_tmpl_id left join x_manufactures xm on xm.id = pt.x_manufacture - where sp.state in ('draft', 'waiting', 'confirmed') + where sp.state in ('draft', 'waiting', 'confirmed', 'assigned') and sp.name like '%OUT%' ) """) -- cgit v1.2.3 From 25ea83dd6a1f8ff272fd086a998904ed792b1041 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 21 Jan 2025 14:57:10 +0700 Subject: fix bug pj --- indoteknik_custom/models/purchasing_job.py | 1 + 1 file changed, 1 insertion(+) diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 4efb0cd4..902bc34b 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -183,6 +183,7 @@ class OutstandingSales(models.Model): join product_template pt on pt.id = pp.product_tmpl_id left join x_manufactures xm on xm.id = pt.x_manufacture where sp.state in ('draft', 'waiting', 'confirmed', 'assigned') + and sm.state in ('draft', 'waiting', 'confirmed', 'partially_available') and sp.name like '%OUT%' ) """) -- cgit v1.2.3 From edfffc3c7166c87c458dd8ae7eba03d61cc82299 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 22 Jan 2025 10:33:07 +0700 Subject: cr bu in --- indoteknik_custom/views/stock_picking.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 8acba608..38ce1af5 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -86,9 +86,9 @@ - + -- cgit v1.2.3 From afbdeb766a04d4c6d31d55151c02320f2b67c3fb Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 23 Jan 2025 09:58:19 +0700 Subject: update bisa edit limit dan durasi tambah ko step dan mba wid --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index b43f56ac..8023dfa7 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -334,13 +334,13 @@ class UserPengajuanTempoRequest(models.Model): @api.onchange('tempo_duration') def _tempo_duration_change(self): for tempo in self: - if tempo.env.user.id not in (7, 377, 12182): + if tempo.env.user.id not in (7, 688, 28, 377, 12182): raise UserError("Durasi tempo hanya bisa di ubah oleh Sales Manager atau Direktur") @api.onchange('tempo_limit') def _onchange_tempo_limit(self): for tempo in self: - if tempo.env.user.id not in (7, 377, 12182): + if tempo.env.user.id not in (7, 688, 28, 377, 12182): raise UserError("Limit tempo hanya bisa diubah oleh Sales Manager atau Direktur") def button_approve(self): for tempo in self: -- cgit v1.2.3 From 2a47fdafcb12440c68e346d35d465b0a0c800945 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 23 Jan 2025 10:03:15 +0700 Subject: push --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/barcoding_product.py | 36 +++++++++++++ indoteknik_custom/models/stock_picking.py | 8 ++- indoteknik_custom/security/ir.model.access.csv | 4 +- indoteknik_custom/views/barcoding_product.xml | 72 ++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 indoteknik_custom/models/barcoding_product.py create mode 100644 indoteknik_custom/views/barcoding_product.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 1ffe9419..67a41a08 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -151,6 +151,7 @@ 'views/form_vendor_approval_multi_reject.xml', 'views/user_pengajuan_tempo.xml', 'views/stock_backorder_confirmation_views.xml', + 'views/barcoding_product.xml', 'report/report.xml', 'report/report_banner_banner.xml', 'report/report_banner_banner2.xml', diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 3990e81c..ed9e91da 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -139,3 +139,4 @@ from . import va_multi_approve from . import va_multi_reject from . import stock_immediate_transfer from . import coretax_fatur +from . import barcoding_product diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py new file mode 100644 index 00000000..41444646 --- /dev/null +++ b/indoteknik_custom/models/barcoding_product.py @@ -0,0 +1,36 @@ +from odoo import models, api, fields +from odoo.exceptions import AccessError, UserError, ValidationError +from datetime import timedelta, date, datetime +import logging + +_logger = logging.getLogger(__name__) + +class BarcodingProduct(models.Model): + _name = "barcoding.product" + _description = "Barcoding Product" + + barcoding_product_line = fields.One2many('barcoding.product.line', 'barcoding_product_id', string='Barcoding Product Lines', auto_join=True) + product_id = fields.Many2one('product.product', string="Product", tracking=3) + quantity = fields.Float(string="Quantity", tracking=3) + + @api.onchange('product_id', 'quantity') + def _onchange_product_or_quantity(self): + """Update barcoding_product_line based on product_id and quantity""" + if self.product_id and self.quantity > 0: + # Clear existing lines + self.barcoding_product_line = [(5, 0, 0)] + + # Add a new line with the current product and quantity + self.barcoding_product_line = [(0, 0, { + 'product_id': self.product_id.id, + 'barcoding_product_id': self.id, + }) for _ in range(int(self.quantity))] + + +class BarcodingProductLine(models.Model): + _name = 'barcoding.product.line' + _description = 'Barcoding Product Line' + _order = 'barcoding_product_id, id' + + barcoding_product_id = fields.Many2one('barcoding.product', string='Barcoding Product Ref', required=True, ondelete='cascade', index=True, copy=False) + product_id = fields.Many2one('product.product', string="Product") \ No newline at end of file diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4c19cb3a..cc86c451 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1164,8 +1164,7 @@ class CheckProduct(models.Model): if not moves: raise UserError(( - "The product '%s' is not available in the related stock picking's moves. " - "Please check and try again." + "The product '%s' tidak ada di operations. " ) % record.product_id.display_name) total_qty_in_moves = sum(moves.mapped('product_uom_qty')) @@ -1184,14 +1183,13 @@ class CheckProduct(models.Model): if total_quantity > total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand: (%s)." + "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) - else: # Check if the quantity exceeds the allowed total if record.quantity > total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand: (%s)." + "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) # Set the quantity to the entered value diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index a26bce31..73877052 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -158,4 +158,6 @@ access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pen 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 access_confirm_approval_wizard,confirm.approval.wizard,model_confirm_approval_wizard,,1,1,1,0 -access_barcode_product,access.barcode.product,model_barcode_product,,1,1,1,1 \ No newline at end of file +access_barcode_product,access.barcode.product,model_barcode_product,,1,1,1,1 +access_barcoding_product,access.barcoding.product,model_barcoding_product,,1,1,1,1 +access_barcoding_product_line,access.barcoding.product.line,model_barcoding_product_line,,1,1,1,1 \ No newline at end of file diff --git a/indoteknik_custom/views/barcoding_product.xml b/indoteknik_custom/views/barcoding_product.xml new file mode 100644 index 00000000..8df007f2 --- /dev/null +++ b/indoteknik_custom/views/barcoding_product.xml @@ -0,0 +1,72 @@ + + + + + barcoding.product.tree + barcoding.product + + + + + + + + + + barcoding.product.line.tree + barcoding.product.line + + + + + + + + + barcoding.product.form + barcoding.product + +
+ + + + + + + + + + + + + +
+
+
+ + + barcoding.product.search.view + barcoding.product + + + + + + + + + Barcoding Product + ir.actions.act_window + barcoding.product + tree,form + + + +
+
-- cgit v1.2.3 From 8af596373c8eb997bbb96cf020f670b6b60b57ca Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 23 Jan 2025 10:06:29 +0700 Subject: push --- indoteknik_custom/models/barcoding_product.py | 3 ++- indoteknik_custom/views/barcoding_product.xml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index 41444646..6bbf9fde 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -33,4 +33,5 @@ class BarcodingProductLine(models.Model): _order = 'barcoding_product_id, id' barcoding_product_id = fields.Many2one('barcoding.product', string='Barcoding Product Ref', required=True, ondelete='cascade', index=True, copy=False) - product_id = fields.Many2one('product.product', string="Product") \ No newline at end of file + product_id = fields.Many2one('product.product', string="Product") + qr_code_variant = fields.Binary("QR Code Variant", related='product_id.qr_code_variant') \ No newline at end of file diff --git a/indoteknik_custom/views/barcoding_product.xml b/indoteknik_custom/views/barcoding_product.xml index 8df007f2..566655ff 100644 --- a/indoteknik_custom/views/barcoding_product.xml +++ b/indoteknik_custom/views/barcoding_product.xml @@ -18,6 +18,7 @@ +
-- cgit v1.2.3 From 9ab92b40a25ad0f677855780a903abc731b6f841 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 23 Jan 2025 11:49:12 +0700 Subject: update code readonly desc to disabled --- indoteknik_custom/views/sale_order.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 008a04ed..6a80d586 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -111,7 +111,7 @@ - + {'readonly': [('desc_updatable', '=', False)]} -- cgit v1.2.3 From 64ef15502562e392ddeb011ced08239f9c6934f3 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 24 Jan 2025 09:15:14 +0700 Subject: update code --- indoteknik_custom/models/ir_actions_report.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/ir_actions_report.py b/indoteknik_custom/models/ir_actions_report.py index 28adcf74..5b49a3ae 100644 --- a/indoteknik_custom/models/ir_actions_report.py +++ b/indoteknik_custom/models/ir_actions_report.py @@ -17,13 +17,13 @@ class IrActionsReport(models.Model): return # ci_vita 7751529082:AAE9XsZa_Pj2Pi2IN1grX98WkwTaIt32pbI & 5081411103 # iman 7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY & 6592318498 - # bot_name_iman = '7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY' - # chat_id_iman = '6592318498' + bot_name_iman = '7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY' + chat_id_iman = '6592318498' bot_name_vita = '7751529082:AAE9XsZa_Pj2Pi2IN1grX98WkwTaIt32pbI' chat_id_vita = '5081411103' - apiURL = f'https://api.telegram.org/bot{bot_name_vita}/sendMessage' + apiURL = f'https://api.telegram.org/bot{bot_name_iman}/sendMessage' try: - requests.post(apiURL, json={'chat_id': chat_id_vita, 'text': sale_order.name + " senilai Rp" + self.format_currency(sale_order.amount_total) + ' untuk customer ' + sale_order.partner_id.name + ' telah dibuat oleh sales ' +sale_order.user_id.name}) + requests.post(apiURL, json={'chat_id': chat_id_iman, 'text': sale_order.name + " senilai Rp" + self.format_currency(sale_order.amount_total) + ' untuk customer ' + sale_order.partner_id.name + ' telah dibuat oleh sales ' +sale_order.user_id.name}) except Exception as e: print(e) -- cgit v1.2.3 From 1a5b7ce42ffd240da52e01758d636a31c0877e6e Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 24 Jan 2025 09:20:45 +0700 Subject: revisi chat id telegram --- indoteknik_custom/models/ir_actions_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/ir_actions_report.py b/indoteknik_custom/models/ir_actions_report.py index 5b49a3ae..83636945 100644 --- a/indoteknik_custom/models/ir_actions_report.py +++ b/indoteknik_custom/models/ir_actions_report.py @@ -18,7 +18,7 @@ class IrActionsReport(models.Model): # ci_vita 7751529082:AAE9XsZa_Pj2Pi2IN1grX98WkwTaIt32pbI & 5081411103 # iman 7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY & 6592318498 bot_name_iman = '7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY' - chat_id_iman = '6592318498' + chat_id_iman = '-1002493002821' bot_name_vita = '7751529082:AAE9XsZa_Pj2Pi2IN1grX98WkwTaIt32pbI' chat_id_vita = '5081411103' apiURL = f'https://api.telegram.org/bot{bot_name_iman}/sendMessage' -- cgit v1.2.3 From d61386fdd9e6082f66ba412dc5ad56e3ff3b2c08 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 24 Jan 2025 09:48:27 +0700 Subject: cr chart of account journal entries --- indoteknik_custom/models/invoice_reklas.py | 4 ++-- indoteknik_custom/models/uangmuka_pembelian.py | 2 +- indoteknik_custom/models/uangmuka_penjualan.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/invoice_reklas.py b/indoteknik_custom/models/invoice_reklas.py index 30da02d1..f5bb5a25 100644 --- a/indoteknik_custom/models/invoice_reklas.py +++ b/indoteknik_custom/models/invoice_reklas.py @@ -49,7 +49,7 @@ class InvoiceReklas(models.TransientModel): if self.reklas_type == 'penjualan': parameter_debit = { 'move_id': account_move.id, - 'account_id': 449, # uang muka penjualan + 'account_id': 668, # penerimaan belum alokasi 'partner_id': invoice.partner_id.id, 'currency_id': 12, 'debit': self.pay_amt, @@ -77,7 +77,7 @@ class InvoiceReklas(models.TransientModel): } parameter_credit = { 'move_id': account_move.id, - 'account_id': 401, + 'account_id': 669, 'partner_id': invoice.partner_id.id, 'currency_id': 12, 'debit': 0, diff --git a/indoteknik_custom/models/uangmuka_pembelian.py b/indoteknik_custom/models/uangmuka_pembelian.py index 204855d3..ba41f814 100644 --- a/indoteknik_custom/models/uangmuka_pembelian.py +++ b/indoteknik_custom/models/uangmuka_pembelian.py @@ -64,7 +64,7 @@ class UangmukaPembelian(models.TransientModel): param_debit = { 'move_id': account_move.id, - 'account_id': 401, # uang muka persediaan barang dagang + 'account_id': 669, # uang muka persediaan barang dagang 'partner_id': partner_id, 'currency_id': 12, 'debit': self.pay_amt, diff --git a/indoteknik_custom/models/uangmuka_penjualan.py b/indoteknik_custom/models/uangmuka_penjualan.py index 5acf604d..a3e95ecd 100644 --- a/indoteknik_custom/models/uangmuka_penjualan.py +++ b/indoteknik_custom/models/uangmuka_penjualan.py @@ -74,7 +74,7 @@ class UangmukaPenjualan(models.TransientModel): # sisa di credit untuk uang muka penjualan, diluar ongkir dan selisih param_credit = { 'move_id': account_move.id, - 'account_id': 449, # uang muka penjualan + 'account_id': 668, # penerimaan belum alokasi 'partner_id': partner_id, 'currency_id': 12, 'debit': 0, -- cgit v1.2.3 From 1dd6174e739bb81c651ead6749200fcd09190ddc Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 24 Jan 2025 13:42:27 +0700 Subject: cr account id --- indoteknik_custom/models/invoice_reklas_penjualan.py | 2 +- indoteknik_custom/models/purchase_order.py | 4 ++-- indoteknik_custom/models/purchase_order_multi_uangmuka.py | 2 +- indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/invoice_reklas_penjualan.py b/indoteknik_custom/models/invoice_reklas_penjualan.py index 5027c8af..80c3ed43 100644 --- a/indoteknik_custom/models/invoice_reklas_penjualan.py +++ b/indoteknik_custom/models/invoice_reklas_penjualan.py @@ -33,7 +33,7 @@ class InvoiceReklasPenjualan(models.TransientModel): parameter_debit = { 'move_id': account_move.id, - 'account_id': 449, # uang muka penjualan + 'account_id': 668, # uang muka penjualan 'partner_id': invoice.partner_id.id, 'currency_id': 12, 'debit': invoice.pay_amt, diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index d487ada3..12a94730 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -178,7 +178,7 @@ class PurchaseOrder(models.Model): 'move_id': bills.id, 'product_id': product_dp.id, # product down payment 'name': '[IT.121456] Down Payment', # product down payment - 'account_id': 401, # Uang Muka persediaan barang dagang + 'account_id': 669, # Uang Muka persediaan barang dagang # 'price_unit': move_line.price_unit, 'quantity': -1, 'product_uom_id': 1, @@ -240,7 +240,7 @@ class PurchaseOrder(models.Model): data_line_bills = { 'move_id': bills.id, 'product_id': product_dp.id, # product down payment - 'account_id': 401, # Uang Muka persediaan barang dagang + 'account_id': 669, # Uang Muka persediaan barang dagang 'quantity': 1, 'product_uom_id': 1, 'tax_ids': [line[0].taxes_id.id for line in self.order_line], diff --git a/indoteknik_custom/models/purchase_order_multi_uangmuka.py b/indoteknik_custom/models/purchase_order_multi_uangmuka.py index dd63e698..0570efd9 100644 --- a/indoteknik_custom/models/purchase_order_multi_uangmuka.py +++ b/indoteknik_custom/models/purchase_order_multi_uangmuka.py @@ -76,7 +76,7 @@ class PurchaseOrderMultiUangmuka(models.TransientModel): param_debit = { 'move_id': account_move.id, - 'account_id': 401, # uang muka persediaan barang dagang + 'account_id': 669, # uang muka persediaan barang dagang 'partner_id': partner_id, 'currency_id': 12, 'debit': order.amount_total, diff --git a/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py b/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py index f9120290..96c2f676 100644 --- a/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py +++ b/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py @@ -68,7 +68,7 @@ class PurchaseOrderMultiUangmukaPenjualan(models.TransientModel): param_credit = { 'move_id': account_move.id, - 'account_id': 449, # uang muka penjualan + 'account_id': 668, # penerimaan belum alokasi 'partner_id': partner_id, 'currency_id': 12, 'debit': 0, -- cgit v1.2.3 From 6a8e2031aedeff2767b73224fd22eacddddc4018 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 24 Jan 2025 15:18:00 +0700 Subject: update api shipment status shipment --- indoteknik_api/controllers/api_v1/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index 110cde8a..2e0c4ad0 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -57,7 +57,7 @@ class StockPicking(controller.Controller): if params['status'] == 'pending': domain += pending_domain elif params['status'] == 'shipment': - domain += shipment_domain + domain += shipment_domain + shipment_domain2 elif params['status'] == 'completed': domain += completed_domain -- cgit v1.2.3 From ca9775c274d5e3de8f60abd66f14fa4fcb44a4ab Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Sat, 25 Jan 2025 08:26:15 +0700 Subject: change email finnace --- indoteknik_custom/views/user_pengajuan_tempo.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/views/user_pengajuan_tempo.xml b/indoteknik_custom/views/user_pengajuan_tempo.xml index 33ad91cf..7f1faa41 100644 --- a/indoteknik_custom/views/user_pengajuan_tempo.xml +++ b/indoteknik_custom/views/user_pengajuan_tempo.xml @@ -269,7 +269,7 @@ Pengajuan Tempo Harus di Periksa! "Indoteknik.com" <noreply@indoteknik.com> sales@indoteknik.com - widyariyanti97@gmail.com, stephan@indoteknik.co.id + finance@indoteknik.co.id, stephan@indoteknik.co.id, sapiabon768@gmail.com -- cgit v1.2.3 From ea81f5a5b5eedfffcfa15a3f752ccc81df6eed04 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 25 Jan 2025 10:34:13 +0700 Subject: CR automatic purchase brand stihl and mikasa --- indoteknik_custom/models/automatic_purchase.py | 141 +++++++++++++------------ 1 file changed, 73 insertions(+), 68 deletions(-) diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 09d283eb..fbdf8dae 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -183,89 +183,94 @@ class AutomaticPurchase(models.Model): def create_po_by_vendor(self, vendor_id): current_time = datetime.now() - if not self.apo_type =='reordering': - name = "/PJ/" - else: - name = "/A/" + name = "/PJ/" if not self.apo_type == 'reordering' else "/A/" PRODUCT_PER_PO = 20 - auto_purchase_line = self.env['automatic.purchase.line'] - last_po = self.env['purchase.order'].search([ - ('partner_id', '=', vendor_id), - ('state', '=', 'done'), - ], order='id desc', limit=1) - - param_header = { - 'partner_id': vendor_id, - '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, - 'from_apo': True, - 'note_description': 'Automatic PO' - } - + # Domain untuk semua baris dengan vendor_id tertentu domain = [ ('automatic_purchase_id', '=', self.id), ('partner_id', '=', vendor_id), ('qty_purchase', '>', 0) ] - products_len = auto_purchase_line.search_count(domain) - page = math.ceil(products_len / PRODUCT_PER_PO) - - # i start from zero (0) - for i in range(page): - new_po = self.env['purchase.order'].create([param_header]) - new_po.payment_term_id = new_po.partner_id.property_supplier_payment_term_id - new_po.name = new_po.name + name + str(i + 1) + # Tambahkan domain khusus untuk brand_id 22 dan 564 + special_brand_domain = domain + [('brand_id', 'in', [22, 564])] + regular_domain = domain + [('brand_id', 'not in', [22, 564])] + + # Fungsi untuk membuat PO berdasarkan domain tertentu + def create_po_for_domain(domain, special_payment_term=False): + products_len = auto_purchase_line.search_count(domain) + page = math.ceil(products_len / PRODUCT_PER_PO) + + for i in range(page): + # Buat PO baru + param_header = { + 'partner_id': vendor_id, + '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, + 'from_apo': True, + 'note_description': 'Automatic PO' + } - self.env['automatic.purchase.match'].create([{ - 'automatic_purchase_id': self.id, - 'order_id': new_po.id - }]) + new_po = self.env['purchase.order'].create([param_header]) - lines = auto_purchase_line.search( - domain, - offset=i * PRODUCT_PER_PO, - limit=PRODUCT_PER_PO - ) + # Set payment_term_id khusus jika diperlukan + if special_payment_term: + new_po.payment_term_id = 29 + else: + new_po.payment_term_id = new_po.partner_id.property_supplier_payment_term_id - lines = auto_purchase_line.search( - domain, - offset=i * PRODUCT_PER_PO, - limit=PRODUCT_PER_PO - ) + new_po.name = new_po.name + name + str(i + 1) + self.env['automatic.purchase.match'].create([{ + 'automatic_purchase_id': self.id, + 'order_id': new_po.id + }]) + + # Ambil baris sesuai halaman + lines = auto_purchase_line.search( + domain, + offset=i * PRODUCT_PER_PO, + limit=PRODUCT_PER_PO + ) + + for line in lines: + product = line.product_id + sales_match = self.env['automatic.purchase.sales.match'].search([ + ('automatic_purchase_id', '=', self.id), + ('product_id', '=', product.id), + ]) + param_line = { + 'order_id': new_po.id, + 'product_id': product.id, + 'product_qty': line.qty_purchase, + 'qty_available_store': product.qty_available_bandengan, + 'suggest': product._get_po_suggest(line.qty_purchase), + 'product_uom_qty': line.qty_purchase, + 'price_unit': line.last_price, + 'ending_price': line.last_price, + 'taxes_id': [line.taxes_id.id] if line.taxes_id else None, + 'so_line_id': sales_match[0].sale_line_id.id if sales_match else None, + 'so_id': sales_match[0].sale_id.id if sales_match else None + } + new_po_line = self.env['purchase.order.line'].create([param_line]) + line.current_po_id = new_po.id + line.current_po_line_id = new_po_line.id + + self.create_purchase_order_sales_match(new_po) + + # Buat PO untuk special brand + if vendor_id == 23: + create_po_for_domain(special_brand_domain, special_payment_term=True) + + # Buat PO untuk regular domain + create_po_for_domain(regular_domain, "") - for line in lines: - product = line.product_id - sales_match = self.env['automatic.purchase.sales.match'].search([ - ('automatic_purchase_id', '=', self.id), - ('product_id', '=', product.id), - ]) - param_line = { - 'order_id': new_po.id, - 'product_id': product.id, - 'product_qty': line.qty_purchase, - 'qty_available_store': product.qty_available_bandengan, - 'suggest': product._get_po_suggest(line.qty_purchase), - 'product_uom_qty': line.qty_purchase, - 'price_unit': line.last_price, - 'ending_price': line.last_price, - 'taxes_id': [line.taxes_id.id] if line.taxes_id else None, - 'so_line_id': sales_match[0].sale_line_id.id if sales_match else None, - 'so_id': sales_match[0].sale_id.id if sales_match else None - } - new_po_line = self.env['purchase.order.line'].create([param_line]) - line.current_po_id = new_po.id - line.current_po_line_id = new_po_line.id - # self.update_purchase_price_so_line(line) - - self.create_purchase_order_sales_match(new_po) def update_purchase_price_so_line(self, apo): sales_match = self.env['automatic.purchase.sales.match'].search([ -- cgit v1.2.3 From 2644e259339cd921babf49218aadbdcedb0c8937 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Tue, 28 Jan 2025 09:46:48 +0700 Subject: change product sla --- indoteknik_api/controllers/api_v1/product.py | 72 ++++++++--------- indoteknik_custom/models/product_sla.py | 114 +++++++++++++++------------ indoteknik_custom/views/product_sla.xml | 5 +- 3 files changed, 100 insertions(+), 91 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py index 7570015f..5d564ff9 100644 --- a/indoteknik_api/controllers/api_v1/product.py +++ b/indoteknik_api/controllers/api_v1/product.py @@ -34,31 +34,34 @@ class Product(controller.Controller): categories.reverse() return self.response(categories, headers=[('Cache-Control', 'max-age=3600, public')]) - @http.route(prefix + 'product/variants/sla', auth='none', type='json', csrf=False, cors='*', methods=['GET', 'OPTIONS']) + @http.route(prefix + 'product/variants/sla', auth='public', methods=['GET', 'OPTIONS']) @controller.Controller.must_authorized() - def get_product_template_sla_by_id(self, **kw): - json_raw = json.loads(request.httprequest.data) + def get_product_template_sla_by_id(self, **kwargs): + body_params = kwargs.get('ids') - ids = json_raw.get('ids') - ids = list(map(int, ids)) + if not body_params: + return self.response('Failed', code=400, description='id is required') - if not ids or not isinstance(ids, list): - return ({'status' : 'Failed','message': 'Parameter "ids" harus berupa list dan tidak boleh kosong.'}) + ids = [int(id.strip()) for id in body_params.split(',') if id.strip().isdigit()] - sla_days = 0 + sla_duration = 0 + sla_unit = 'Hari' + include_instant = True products = request.env['product.product'].search([('id', 'in', ids)]) if len(products) < 1: - return ({ - 'status' : 'Failed', - 'message' : 'Produk Tidak Di Temukan.' - }) + return self.response( + 'Failed', + code=400, + description='Produk Tidak Di Temukan.' + ) product_slas = request.env['product.sla'].search([('product_variant_id', 'in', ids)]) if len(product_slas) < 1: - return ({ - 'status' : 'Failed', - 'message' : 'Produk Tidak Di Temukan.' - }) + return self.response( + 'Failed', + code=400, + description='SLA Tidak Di Temukan.' + ) # Mapping SLA untuk mempermudah lookup sla_map = {sla.product_variant_id.id: sla for sla in product_slas} @@ -66,24 +69,20 @@ class Product(controller.Controller): for product in products: product_sla = sla_map.get(product.id) if product_sla: - sla_days = max(sla_days, product_sla.sla_vendor_id.duration) + sla_duration = max(sla_duration, int(product_sla.sla)) + sla_unit = product_sla.sla_vendor_id.unit if product.qty_free_bandengan < 1 : if product_sla.sla_vendor_id.unit != 'jam': - return ({ - 'status' : 'Success', - 'data' : [{ - 'include_instant': False, - 'sla_days': sla_days - }], - }) + include_instant = False + break + # Jika semua loop selesai tanpa include_instant menjadi False - return ({ - 'status' : 'Success', - 'data' : [{ - 'include_instant': True, - 'sla_days': sla_days - }], - }) + return self.response({ + 'include_instant': include_instant, + 'sla_duration': sla_duration, + 'sla_unit': sla_unit + } + ) @http.route(prefix + 'product_variant//stock', auth='public', methods=['GET', 'OPTIONS']) @controller.Controller.must_authorized() @@ -98,18 +97,12 @@ class Product(controller.Controller): ('product_variant_id', '=', id), ('write_date', '>=', date_7_days_ago.strftime("%Y-%m-%d %H:%M:%S")) ], limit=1) - - include_instant = False qty_available = product.qty_free_bandengan - if qty_available > 0 : - include_instant = True - else : + if qty_available < 1 : qty_available = 0 - if product_sla.sla_vendor_id.unit == 'jam': - include_instant = True qty = 0 sla_date = '-' @@ -150,8 +143,7 @@ class Product(controller.Controller): data = { 'qty': qty, - 'sla_date': sla_date, - 'can_instant': include_instant + 'sla_date': sla_date } return self.response(data, headers=[('Cache-Control', 'max-age=600, private')]) diff --git a/indoteknik_custom/models/product_sla.py b/indoteknik_custom/models/product_sla.py index 988aa78f..04ad2ffd 100644 --- a/indoteknik_custom/models/product_sla.py +++ b/indoteknik_custom/models/product_sla.py @@ -14,51 +14,53 @@ class ProductSla(models.Model): product_variant_id = fields.Many2one('product.product',string='Product') sla_vendor_id = fields.Many2one('vendor.sla',string='Vendor', readonly=True) sla_vendor_duration = fields.Char(string='AVG Leadtime', related='sla_vendor_id.duration_unit') - avg_leadtime = fields.Char(string='AVG Leadtime', readonly=True) - leadtime = fields.Char(string='Leadtime', readonly=True) + sla_logistic = fields.Char(string='SLA Logistic', readonly=True) + sla_logistic_unit = fields.Selection( + [('jam', 'Jam'),('hari', 'Hari')], + string="SLA Logistic Time" + ) + sla_logistic_duration_unit = fields.Char(string="SLA Logistic Duration (Unit)") sla = fields.Char(string='SLA', readonly=True) version = fields.Integer(string="Version", compute="_compute_version") def _compute_version(self): for sla in self: sla.version = sla.product_variant_id.sla_version + + - def generate_product_variant_id_sla(self, limit=5000): - # Filter produk non-Altama - + def generate_product_variant_id_sla(self, limit=500): + offset = 0 + # while True: products = self.env['product.product'].search([ - ('x_manufacture', 'not in', [10, 122, 89]), - ('location_id', '=', 57), - ('stock_move_ids', '!=', False), - ], order='sla_version asc', limit=limit) + ('active', '=', True), + ('sale_ok', '=', True), + ], order='sla_version asc', limit=limit, offset=offset) + + # if not products: + # break - i = 1 for product in products: - _logger.info(f'Product SLA: {i}/{len(products)}') - i += 1 - product.sla_version += 1 + _logger.info(f'Memproses SLA untuk produk ID {product.id}, versi {product.sla_version}') product_sla = self.search([('product_variant_id', '=', product.id)], limit=1) - print(product_sla.id, product.id) if not product_sla: - product_sla = self.env['product.sla'].create({ - 'product_variant_id': product.id, - }) - + product_sla = self.create({'product_variant_id': product.id}) + product_sla.generate_product_sla() + # Tandai produk sebagai sudah diproses + product.sla_version += 1 + + offset += limit + + def generate_product_sla(self): - self.avg_leadtime = '-' - self.sla = '-' + # self.sla_logistic = '-' + # self.sla_logistic_duration_unit = '-' + # self.sla = '-' product = self.product_variant_id - - # qty_available = 0 - # qty_available = product.qty_onhand_bandengan - - - # if qty_available > 0: - # self.sla = 1 q_vendor = [ ('product_id', '=', product.id), @@ -68,6 +70,8 @@ class ProductSla(models.Model): vendor = self.env['purchase.pricelist'].search(q_vendor) vendor_duration = 0 + + #SLA Vendor if vendor: vendor_sla = self.env['vendor.sla'].search([('id_vendor', '=', vendor.vendor_id.id)], limit=1) sla_vendor = int(vendor_sla.duration) if vendor_sla else 0 @@ -78,28 +82,40 @@ class ProductSla(models.Model): vendor_duration = vendor_sla.duration * 60 self.sla_vendor_id = vendor_sla.id if vendor_sla else False + #SLA Logistik selalu 1 hari + estimation_sla = (1 * 24 * 60) + vendor_duration + estimation_sla_days = estimation_sla / (24 * 60) + self.sla = math.ceil(estimation_sla_days) + self.sla_logistic = int(1) + self.sla_logistic_unit = 'hari' + self.sla_logistic_duration_unit = '1 hari' + else: + self.unlink() + else: + self.unlink() + - query = [ - ('product_id', '=', product.id), - ('picking_id', '!=', False), - ('picking_id.location_id', '=', 57), - ('picking_id.state', 'not in', ['cancel']) - ] - picking = self.env['stock.move.line'].search(query) + # query = [ + # ('product_id', '=', product.id), + # ('picking_id', '!=', False), + # ('picking_id.location_id', '=', 57), + # ('picking_id.state', 'not in', ['cancel']) + # ] + # picking = self.env['stock.move.line'].search(query) - leadtimes=[] - for stock in picking: - date_delivered = stock.picking_id.driver_departure_date - date_do_ready = stock.picking_id.date_reserved - if date_delivered and date_do_ready: - leadtime = date_delivered - date_do_ready - leadtime_in_days = leadtime.days - leadtimes.append(leadtime_in_days) + # leadtimes=[] + # for stock in picking: + # date_delivered = stock.picking_id.driver_departure_date + # date_do_ready = stock.picking_id.date_reserved + # if date_delivered and date_do_ready: + # leadtime = date_delivered - date_do_ready + # leadtime_in_days = leadtime.days + # leadtimes.append(leadtime_in_days) - if len(leadtimes) > 0: - avg_leadtime = sum(leadtimes) / len(leadtimes) - rounded_leadtime = math.ceil(avg_leadtime) - estimation_sla = ((rounded_leadtime * 24 * 60) + vendor_duration)/2 - estimation_sla_days = estimation_sla / (24 * 60) - self.sla = math.ceil(estimation_sla_days) - self.avg_leadtime = int(rounded_leadtime) \ No newline at end of file + # if len(leadtimes) > 0: + # avg_leadtime = sum(leadtimes) / len(leadtimes) + # rounded_leadtime = math.ceil(avg_leadtime) + # estimation_sla = ((rounded_leadtime * 24 * 60) + vendor_duration)/2 + # estimation_sla_days = estimation_sla / (24 * 60) + # self.sla = math.ceil(estimation_sla_days) + # self.avg_leadtime = int(rounded_leadtime) \ No newline at end of file diff --git a/indoteknik_custom/views/product_sla.xml b/indoteknik_custom/views/product_sla.xml index 3722ef3d..9179730f 100644 --- a/indoteknik_custom/views/product_sla.xml +++ b/indoteknik_custom/views/product_sla.xml @@ -8,7 +8,7 @@ - + @@ -23,7 +23,8 @@ - + + -- cgit v1.2.3 From 346b6dc89cbde3640413714175cdc438544a664c Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 28 Jan 2025 14:58:05 +0700 Subject: save response --- indoteknik_custom/models/stock_picking.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cd330aeb..6967e1a3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -165,6 +165,11 @@ 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") + # 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") + def _compute_lalamove_image_html(self): for record in self: if record.lalamove_image_url: @@ -387,7 +392,7 @@ class StockPicking(models.Model): } # Cek jika pengiriman instant atau same_day - if "instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type: + if self.sale_id.delivery_service_type and ("instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): payload.update({ "origin_note": "BELAKANG INDOMARET", "courier_company": self.carrier_id.name.lower(), @@ -404,10 +409,19 @@ class StockPicking(models.Model): # Kirim request ke Biteship response = requests.post(url, headers=headers, json=payload) - if response.status_code == 201: - return response.json() + if response.status_code == 200: + data = response.json() + + self.biteship_id = data.get("id", "") + self.biteship_tracking_id = data.get("tracking_id", "") + self.biteship_waybill_id = data.get("waybill_id", "") + + return data else: - raise UserError(f"Error saat mengirim ke Biteship: {response.content}") + error_data = response.json() + error_message = error_data.get("error", "Unknown error") + error_code = error_data.get("code", "No code provided") + raise UserError(f"Error saat mengirim ke Biteship: {error_message} (Code: {error_code})") @api.constrains('driver_departure_date') def constrains_driver_departure_date(self): -- cgit v1.2.3 From 364eb8f589e5070c06e10bb5895e20e69dad8249 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 28 Jan 2025 15:23:38 +0700 Subject: add shipping_method_picking on sale order --- indoteknik_custom/models/sale_order.py | 9 +++++++++ indoteknik_custom/views/sale_order.xml | 1 + 2 files changed, 10 insertions(+) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 48195b77..98468c4b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -144,6 +144,15 @@ class SaleOrder(models.Model): ('PNR', 'Pareto Non Repeating'), ('NP', 'Non Pareto') ]) + shipping_method_picking = fields.Char(string='Shipping Method Picking', compute='_compute_shipping_method_picking') + + def _compute_shipping_method_picking(self): + for order in self: + if order.picking_ids: + carrier_names = order.picking_ids.mapped('carrier_id.name') + order.shipping_method_picking = ', '.join(filter(None, carrier_names)) + else: + order.shipping_method_picking = False @api.onchange('payment_status') def _is_continue_transaction(self): diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 6a80d586..2a46901a 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -296,6 +296,7 @@ + -- cgit v1.2.3 From 888c71b3a5fe4ff4a338a2d13718b8624b1348d6 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 30 Jan 2025 09:32:05 +0700 Subject: update ambil tempo duration tempo --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index 8023dfa7..29cf391c 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -71,7 +71,7 @@ class UserPengajuanTempoRequest(models.Model): website_tempo = fields.Char(string='Website', related='pengajuan_tempo_id.website_tempo', store=True, tracking=True, readonly=False) portal = fields.Boolean(string='Portal Website', related='pengajuan_tempo_id.portal', store=True, tracking=True, readonly=False) estimasi_tempo = fields.Char(string='Estimasi Pembelian Pertahun', related='pengajuan_tempo_id.estimasi_tempo', store=True, tracking=True, readonly=False) - tempo_duration_origin = fields.Many2one('account.payment.term', string='Durasi Tempo', related='pengajuan_tempo_id.tempo_duration', store=True, tracking=True, readonly=False, domain=[('id', 'in', [24, 25, 29, 32])]) + tempo_duration_origin = fields.Many2one('account.payment.term', string='Durasi Tempo', related='tempo_duration', store=True, tracking=True, readonly=False, domain=[('id', 'in', [24, 25, 29, 32])]) tempo_limit_origin = fields.Char(string='Limit Tempo', related='pengajuan_tempo_id.tempo_limit' , store=True, tracking=True, readonly=False) category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan', related='pengajuan_tempo_id.category_produk_ids', readonly=False) @@ -596,7 +596,7 @@ class UserPengajuanTempoRequest(models.Model): attachment_ids=[self.user_company_id.dokumen_tempat_bekerja.id]) # self.user_company_id.active = True # user.send_company_request_approve_mail() - self.user_company_id.property_payment_term_id = self.pengajuan_tempo_id.tempo_duration.id + self.user_company_id.property_payment_term_id = self.tempo_duration.id self.user_company_id.active_limit = True self.user_company_id.warning_stage = float(limit_tempo) - (float(limit_tempo)/2) self.user_company_id.blocking_stage = limit_tempo -- cgit v1.2.3 From dcbd88ea9ea1977c047425e31559fb2e5cee3de2 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 30 Jan 2025 10:53:45 +0700 Subject: add validation on write and create function --- indoteknik_custom/models/product_template.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 29608297..d4b0abf4 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -67,6 +67,23 @@ class ProductTemplate(models.Model): print_barcode = fields.Boolean(string='Print Barcode', default=True) # qr_code = fields.Binary("QR Code", compute='_compute_qr_code') + @api.model + def create(self, vals): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + if self.env.user.id not in users_in_group.mapped('id'): + raise UserError('Hanya MD yang bisa membuat Product') + result = super(ProductTemplate, self).create(vals) + return result + + def write(self, values): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + if self.env.user.id not in users_in_group.mapped('id'): + raise UserError('Hanya MD yang bisa mengedit Product') + result = super(ProductTemplate, self).write(values) + return result + # def _compute_qr_code(self): # for rec in self.product_variant_ids: # qr = qrcode.QRCode( @@ -403,6 +420,23 @@ class ProductProduct(models.Model): merchandise_ok = fields.Boolean(string='Product Promotion') qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') + @api.model + def create(self, vals): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + if self.env.user.id not in users_in_group.mapped('id'): + raise UserError('Hanya MD yang bisa membuat Product') + result = super(ProductProduct, self).create(vals) + return result + + def write(self, values): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + if self.env.user.id not in users_in_group.mapped('id'): + raise UserError('Hanya MD yang bisa mengedit Product') + result = super(ProductProduct, self).write(values) + return result + def _compute_qr_code_variant(self): for rec in self: # Skip inactive variants -- cgit v1.2.3 From f50f7d570eaa66552e6e91cfd9a29942a7ea3c36 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 30 Jan 2025 11:00:36 +0700 Subject: fix bug --- indoteknik_custom/models/product_template.py | 60 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index d4b0abf4..151513e8 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -67,22 +67,22 @@ class ProductTemplate(models.Model): print_barcode = fields.Boolean(string='Print Barcode', default=True) # qr_code = fields.Binary("QR Code", compute='_compute_qr_code') - @api.model - def create(self, vals): - group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id - users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) - if self.env.user.id not in users_in_group.mapped('id'): - raise UserError('Hanya MD yang bisa membuat Product') - result = super(ProductTemplate, self).create(vals) - return result + # @api.model + # def create(self, vals): + # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + # if self.env.user.id not in users_in_group.mapped('id'): + # raise UserError('Hanya MD yang bisa membuat Product') + # result = super(ProductTemplate, self).create(vals) + # return result - def write(self, values): - group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id - users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) - if self.env.user.id not in users_in_group.mapped('id'): - raise UserError('Hanya MD yang bisa mengedit Product') - result = super(ProductTemplate, self).write(values) - return result + # def write(self, values): + # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + # if self.env.user.id not in users_in_group.mapped('id'): + # raise UserError('Hanya MD yang bisa mengedit Product') + # result = super(ProductTemplate, self).write(values) + # return result # def _compute_qr_code(self): # for rec in self.product_variant_ids: @@ -420,22 +420,22 @@ class ProductProduct(models.Model): merchandise_ok = fields.Boolean(string='Product Promotion') qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') - @api.model - def create(self, vals): - group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id - users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) - if self.env.user.id not in users_in_group.mapped('id'): - raise UserError('Hanya MD yang bisa membuat Product') - result = super(ProductProduct, self).create(vals) - return result + # @api.model + # def create(self, vals): + # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + # if self.env.user.id not in users_in_group.mapped('id'): + # raise UserError('Hanya MD yang bisa membuat Product') + # result = super(ProductProduct, self).create(vals) + # return result - def write(self, values): - group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id - users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) - if self.env.user.id not in users_in_group.mapped('id'): - raise UserError('Hanya MD yang bisa mengedit Product') - result = super(ProductProduct, self).write(values) - return result + # def write(self, values): + # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + # if self.env.user.id not in users_in_group.mapped('id'): + # raise UserError('Hanya MD yang bisa mengedit Product') + # result = super(ProductProduct, self).write(values) + # return result def _compute_qr_code_variant(self): for rec in self: -- cgit v1.2.3 From 2ceef7e7e73a91ee513787071ba4537c7c263349 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 30 Jan 2025 11:37:15 +0700 Subject: fix bug validation create and write --- indoteknik_custom/models/product_template.py | 64 +++++++++++++++------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 151513e8..99a89d74 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -67,22 +67,24 @@ class ProductTemplate(models.Model): print_barcode = fields.Boolean(string='Print Barcode', default=True) # qr_code = fields.Binary("QR Code", compute='_compute_qr_code') - # @api.model - # def create(self, vals): - # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id - # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) - # if self.env.user.id not in users_in_group.mapped('id'): - # raise UserError('Hanya MD yang bisa membuat Product') - # result = super(ProductTemplate, self).create(vals) - # return result + @api.model + def create(self, vals): + 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 not in users_in_group.mapped('id') and active_model == None: + raise UserError('Hanya MD yang bisa membuat Product') + result = super(ProductTemplate, self).create(vals) + return result - # def write(self, values): - # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id - # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) - # if self.env.user.id not in users_in_group.mapped('id'): - # raise UserError('Hanya MD yang bisa mengedit Product') - # result = super(ProductTemplate, self).write(values) - # return result + def write(self, values): + 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 not in users_in_group.mapped('id') and active_model == None: + raise UserError('Hanya MD yang bisa mengedit Product') + result = super(ProductTemplate, self).write(values) + return result # def _compute_qr_code(self): # for rec in self.product_variant_ids: @@ -420,22 +422,24 @@ class ProductProduct(models.Model): merchandise_ok = fields.Boolean(string='Product Promotion') qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') - # @api.model - # def create(self, vals): - # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id - # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) - # if self.env.user.id not in users_in_group.mapped('id'): - # raise UserError('Hanya MD yang bisa membuat Product') - # result = super(ProductProduct, self).create(vals) - # return result + @api.model + def create(self, vals): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + active_model = self.env.context.get('active_model') + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + if self.env.user.id not in users_in_group.mapped('id') and active_model == None: + raise UserError('Hanya MD yang bisa membuat Product') + result = super(ProductProduct, self).create(vals) + return result - # def write(self, values): - # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id - # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) - # if self.env.user.id not in users_in_group.mapped('id'): - # raise UserError('Hanya MD yang bisa mengedit Product') - # result = super(ProductProduct, self).write(values) - # return result + def write(self, values): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + active_model = self.env.context.get('active_model') + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + if self.env.user.id not in users_in_group.mapped('id') and active_model == None: + raise UserError('Hanya MD yang bisa mengedit Product') + result = super(ProductProduct, self).write(values) + return result def _compute_qr_code_variant(self): for rec in self: -- cgit v1.2.3 From b0152e913e55d4061abe1a0e96c558f62a99a830 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 30 Jan 2025 13:48:19 +0700 Subject: cr on account move line and add tracking on shipping paid by and shipping covered by on sale order --- indoteknik_custom/models/account_move_line.py | 6 ++++++ indoteknik_custom/models/sale_order.py | 4 ++-- indoteknik_custom/views/account_move.xml | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/account_move_line.py b/indoteknik_custom/models/account_move_line.py index a4b25109..c4a65209 100644 --- a/indoteknik_custom/models/account_move_line.py +++ b/indoteknik_custom/models/account_move_line.py @@ -7,6 +7,12 @@ class AccountMoveLine(models.Model): cost_centre_id = fields.Many2one('cost.centre', string='Cost Centre') is_required = fields.Boolean(string='Is Required', compute='_compute_is_required') analytic_account_ids = fields.Many2many('account.analytic.account', string='Analytic Account') + line_no = fields.Integer('No', default=0, compute='_compute_line_no') + + def _compute_line_no(self): + if self.move_id: + for index, line in enumerate(self.move_id.invoice_line_ids, start=1): + line.line_no = index @api.onchange('account_id') def _onchange_account_id(self): diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 98468c4b..132aa397 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -28,11 +28,11 @@ class SaleOrder(models.Model): shipping_cost_covered = fields.Selection([ ('indoteknik', 'Indoteknik'), ('customer', 'Customer') - ], string='Shipping Covered by', help='Siapa yang menanggung biaya ekspedisi?', copy=False) + ], string='Shipping Covered by', help='Siapa yang menanggung biaya ekspedisi?', copy=False, tracking=3) shipping_paid_by = fields.Selection([ ('indoteknik', 'Indoteknik'), ('customer', 'Customer') - ], string='Shipping Paid by', help='Siapa yang talangin dulu Biaya ekspedisi-nya?', copy=False) + ], string='Shipping Paid by', help='Siapa yang talangin dulu Biaya ekspedisi-nya?', copy=False, tracking=3) sales_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) have_outstanding_invoice = fields.Boolean('Have Outstanding Invoice', compute='_have_outstanding_invoice') have_outstanding_picking = fields.Boolean('Have Outstanding Picking', compute='_have_outstanding_picking') diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml index 36b292e8..50a34e11 100644 --- a/indoteknik_custom/views/account_move.xml +++ b/indoteknik_custom/views/account_move.xml @@ -137,6 +137,9 @@ {'column_invisible': [('parent.move_type', '!=', 'in_invoice')]} + + + -- cgit v1.2.3 From f1927a4220d546756bc3a1cc5666aa2ef2c8d816 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 30 Jan 2025 14:59:15 +0700 Subject: fix bug --- indoteknik_custom/models/product_template.py | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 99a89d74..efacb95f 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -77,14 +77,14 @@ class ProductTemplate(models.Model): result = super(ProductTemplate, self).create(vals) return result - def write(self, values): - 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 not in users_in_group.mapped('id') and active_model == None: - raise UserError('Hanya MD yang bisa mengedit Product') - result = super(ProductTemplate, self).write(values) - return result + # def write(self, values): + # 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 not in users_in_group.mapped('id') and active_model == None: + # raise UserError('Hanya MD yang bisa mengedit Product') + # result = super(ProductTemplate, self).write(values) + # return result # def _compute_qr_code(self): # for rec in self.product_variant_ids: @@ -432,14 +432,14 @@ class ProductProduct(models.Model): result = super(ProductProduct, self).create(vals) return result - def write(self, values): - group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id - active_model = self.env.context.get('active_model') - users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) - if self.env.user.id not in users_in_group.mapped('id') and active_model == None: - raise UserError('Hanya MD yang bisa mengedit Product') - result = super(ProductProduct, self).write(values) - return result + # def write(self, values): + # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + # active_model = self.env.context.get('active_model') + # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + # if self.env.user.id not in users_in_group.mapped('id') and active_model == None: + # raise UserError('Hanya MD yang bisa mengedit Product') + # result = super(ProductProduct, self).write(values) + # return result def _compute_qr_code_variant(self): for rec in self: -- cgit v1.2.3 From b547143489e5bef27425199b36a9dd3984429421 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 30 Jan 2025 15:35:20 +0700 Subject: label entries line same with ref move entry --- indoteknik_custom/models/account_move.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 85ed1d54..7760c69d 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -65,6 +65,13 @@ class AccountMove(models.Model): other_subtotal = fields.Float(string="Other Subtotal", compute='compute_other_subtotal') other_taxes = fields.Float(string="Other Taxes", compute='compute_other_taxes') + def _update_line_name_from_ref(self): + """Update all account.move.line name fields with ref from account.move""" + for move in self: + if move.move_type == 'entry' and move.ref and move.line_ids: + for line in move.line_ids: + line.name = move.ref + def compute_other_taxes(self): for rec in self: rec.other_taxes = rec.other_subtotal * 0.12 @@ -107,6 +114,7 @@ class AccountMove(models.Model): def create(self, vals): vals['nomor_kwitansi'] = self.env['ir.sequence'].next_by_code('nomor.kwitansi') or '0' result = super(AccountMove, self).create(vals) + result._update_line_name_from_ref() return result def compute_so_shipping_paid_by(self): -- cgit v1.2.3 From d7a18a90adc0502831eabd1e5940677161ddd4bf Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 30 Jan 2025 16:56:07 +0700 Subject: add filter searching picking on report logbook sj --- indoteknik_custom/models/report_logbook_sj.py | 2 ++ indoteknik_custom/views/report_logbook_sj.xml | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/indoteknik_custom/models/report_logbook_sj.py b/indoteknik_custom/models/report_logbook_sj.py index a1b6299c..17119c12 100644 --- a/indoteknik_custom/models/report_logbook_sj.py +++ b/indoteknik_custom/models/report_logbook_sj.py @@ -29,6 +29,8 @@ class ReportLogbookSJ(models.Model): string='Status', tracking=True, ) + + sj_number = fields.Char(string='Picking', related='report_logbook_sj_line.name') count_line = fields.Char(string='Count Line', compute='_compute_count_line') diff --git a/indoteknik_custom/views/report_logbook_sj.xml b/indoteknik_custom/views/report_logbook_sj.xml index 896594bb..94f6c2ab 100644 --- a/indoteknik_custom/views/report_logbook_sj.xml +++ b/indoteknik_custom/views/report_logbook_sj.xml @@ -77,6 +77,16 @@ + + report.logbook.sj.search.view + report.logbook.sj + + + + + + + Report Logbook SJ ir.actions.act_window -- cgit v1.2.3 From de4404c4b60b0859a4cbae836cc4c99d806bf697 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 31 Jan 2025 10:01:49 +0700 Subject: fix bug --- indoteknik_custom/models/account_move_line.py | 2 +- indoteknik_custom/models/stock_picking.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/account_move_line.py b/indoteknik_custom/models/account_move_line.py index c4a65209..9ab9620b 100644 --- a/indoteknik_custom/models/account_move_line.py +++ b/indoteknik_custom/models/account_move_line.py @@ -10,7 +10,7 @@ class AccountMoveLine(models.Model): line_no = fields.Integer('No', default=0, compute='_compute_line_no') def _compute_line_no(self): - if self.move_id: + if self.move_id and self.move_id.move_type == 'out_invoice': for index, line in enumerate(self.move_id.invoice_line_ids, start=1): line.line_no = index diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cc86c451..9af74cd3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -467,7 +467,7 @@ class StockPicking(models.Model): def check_state_reserve(self): pickings = self.search([ - ('state', 'not in', ['cancel', 'draft', 'done']), + ('state', 'not in', ['cancel', 'done']), ('picking_type_code', '=', 'outgoing') ]) -- cgit v1.2.3 From c131fcf6f0fd3aa8bd4d3a241aabe577bbf57137 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 31 Jan 2025 10:19:45 +0700 Subject: fix bug --- indoteknik_custom/models/account_move_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/account_move_line.py b/indoteknik_custom/models/account_move_line.py index 9ab9620b..c4a65209 100644 --- a/indoteknik_custom/models/account_move_line.py +++ b/indoteknik_custom/models/account_move_line.py @@ -10,7 +10,7 @@ class AccountMoveLine(models.Model): line_no = fields.Integer('No', default=0, compute='_compute_line_no') def _compute_line_no(self): - if self.move_id and self.move_id.move_type == 'out_invoice': + if self.move_id: for index, line in enumerate(self.move_id.invoice_line_ids, start=1): line.line_no = index -- cgit v1.2.3 From 9b4b186d5ac43f1e823ddf37d60f37980f3e721c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 31 Jan 2025 10:27:51 +0700 Subject: fix bug --- indoteknik_custom/models/account_move_line.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/account_move_line.py b/indoteknik_custom/models/account_move_line.py index c4a65209..568c9b85 100644 --- a/indoteknik_custom/models/account_move_line.py +++ b/indoteknik_custom/models/account_move_line.py @@ -9,9 +9,10 @@ class AccountMoveLine(models.Model): analytic_account_ids = fields.Many2many('account.analytic.account', string='Analytic Account') line_no = fields.Integer('No', default=0, compute='_compute_line_no') + @api.depends('move_id.invoice_line_ids') def _compute_line_no(self): - if self.move_id: - for index, line in enumerate(self.move_id.invoice_line_ids, start=1): + for move in self.mapped('move_id'): + for index, line in enumerate(move.invoice_line_ids, start=1): line.line_no = index @api.onchange('account_id') -- cgit v1.2.3 From 8cba896edb13b2b2911e487aea562553d3b17025 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 31 Jan 2025 10:34:41 +0700 Subject: trying to fix bug --- indoteknik_custom/models/account_move_line.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/account_move_line.py b/indoteknik_custom/models/account_move_line.py index 568c9b85..3c352560 100644 --- a/indoteknik_custom/models/account_move_line.py +++ b/indoteknik_custom/models/account_move_line.py @@ -12,9 +12,12 @@ class AccountMoveLine(models.Model): @api.depends('move_id.invoice_line_ids') def _compute_line_no(self): for move in self.mapped('move_id'): - for index, line in enumerate(move.invoice_line_ids, start=1): - line.line_no = index - + if move.move_type == 'out_invoice' and move.invoice_line_ids: + for index, line in enumerate(move.invoice_line_ids, start=1): + line.line_no = index + else: + for index, line in enumerate(move.line_ids, start=1): + line.line_no = index @api.onchange('account_id') def _onchange_account_id(self): for account in self: -- cgit v1.2.3 From 57b2ce27d2892f04e0503ac4543d23245bee639e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 31 Jan 2025 11:00:25 +0700 Subject: fix bug --- indoteknik_custom/models/account_move_line.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/indoteknik_custom/models/account_move_line.py b/indoteknik_custom/models/account_move_line.py index 3c352560..37f7c77c 100644 --- a/indoteknik_custom/models/account_move_line.py +++ b/indoteknik_custom/models/account_move_line.py @@ -7,17 +7,8 @@ class AccountMoveLine(models.Model): cost_centre_id = fields.Many2one('cost.centre', string='Cost Centre') is_required = fields.Boolean(string='Is Required', compute='_compute_is_required') analytic_account_ids = fields.Many2many('account.analytic.account', string='Analytic Account') - line_no = fields.Integer('No', default=0, compute='_compute_line_no') + line_no = fields.Integer('No', default=0) - @api.depends('move_id.invoice_line_ids') - def _compute_line_no(self): - for move in self.mapped('move_id'): - if move.move_type == 'out_invoice' and move.invoice_line_ids: - for index, line in enumerate(move.invoice_line_ids, start=1): - line.line_no = index - else: - for index, line in enumerate(move.line_ids, start=1): - line.line_no = index @api.onchange('account_id') def _onchange_account_id(self): for account in self: -- cgit v1.2.3 From b4249a4dbed1f982ce2355ea7b8245dd1c44da8d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 31 Jan 2025 11:20:33 +0700 Subject: fix send mail bills --- indoteknik_custom/models/stock_immediate_transfer.py | 1 - indoteknik_custom/models/stock_picking.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index 4be0dff2..21210619 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -16,7 +16,6 @@ class StockImmediateTransfer(models.TransientModel): pickings_not_to_do |= line.picking_id for picking in pickings_to_do: - picking.send_mail_bills() # If still in draft => confirm and assign if picking.state == 'draft': picking.action_confirm() diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 9af74cd3..ce198be3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -811,6 +811,7 @@ class StockPicking(models.Model): self.calculate_line_no() self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' + self.send_mail_bills() return res -- cgit v1.2.3 From 74796f3390ad90e2ae981351cd0491aca6dccc9c Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 31 Jan 2025 16:45:07 +0700 Subject: add ready to ship date --- indoteknik_custom/models/product_template.py | 13 +++++++ indoteknik_custom/models/sale_order.py | 57 +++++++++++++++++++++++++++- indoteknik_custom/models/stock_picking.py | 29 +++++++++++++- indoteknik_custom/views/product_product.xml | 8 ++++ indoteknik_custom/views/sale_order.xml | 1 + indoteknik_custom/views/stock_picking.xml | 5 ++- 6 files changed, 109 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 5bedae13..8c57774e 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -403,6 +403,19 @@ class ProductProduct(models.Model): merchandise_ok = fields.Boolean(string='Product Promotion') qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') + def generate_product_sla(self): + product_variant_ids = self.env.context.get('active_ids', []) + product_variant = self.search([('id', 'in', product_variant_ids)]) + sla_record = self.env['product.sla'].search([('product_variant_id', '=', product_variant.id)], limit=1) + + if sla_record: + sla_record.generate_product_sla() + else: + new_sla_record = self.env['product.sla'].create({ + 'product_variant_id': product_variant.id, + }) + new_sla_record.generate_product_sla() + def _compute_qr_code_variant(self): for rec in self: # Skip inactive variants diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 48195b77..32e6f11f 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -144,6 +144,16 @@ class SaleOrder(models.Model): ('PNR', 'Pareto Non Repeating'), ('NP', 'Non Pareto') ]) + estimated_ready_ship_date = fields.Datetime( + string='ET Ready to Ship', + copy=False, + store=True + ) + expected_ready_ship_date = fields.Datetime( + string='ET Ready to Ship FIX', + copy=False, + store=True + ) @api.onchange('payment_status') def _is_continue_transaction(self): @@ -377,6 +387,45 @@ class SaleOrder(models.Model): else: rec.eta_date = False + def _compute_etrts_date(self): + max_slatime = 100 + + # untuk setiap produk dalam so di ambil sla nya sla paling kecil itulah yang dipakai untuk tambah ke + max_leadtime = 0 + + for line in self.order_line: + product_sla = self.env['product.sla'].search([('product_variant_id', '=', line.product_id.id)]) + slatime = int(product_sla.sla) or 1 + max_slatime = max(max_slatime, slatime) + + for rec in self: + if rec.date_order: + eta_date = datetime.now() + timedelta(days=max_slatime) + rec.estimated_ready_ship_date = eta_date + if not rec.expected_ready_ship_date: + rec.expected_ready_ship_date = eta_date + # else: + # rec.estimated_ready_ship_date = False + + def _set_etrts_date(self): + for order in self: + if order.state in ('done', 'cancel', 'sale'): + raise UserError(_("You cannot change the Estimated Ready To Ship Date on a done, sale or cancelled order.")) + # order.move_lines.write({'estimated_ready_ship_date': order.estimated_ready_ship_date}) + + @api.onchange('estimated_ready_ship_date') + def _onchange_estimated_ready_ship_date(self): + for record in self: + if (record.estimated_ready_ship_date and record.expected_ready_ship_date): + if(record.estimated_ready_ship_date < record.expected_ready_ship_date): + return { + 'warning': { + 'title': _('Requested date is too soon.'), + 'message': _("The delivery date is sooner than the expected date." + "You may be unable to honor the delivery date.") + } + } + def _prepare_invoice(self): """ Prepare the dict of values to create the new invoice for a sales order. This method may be @@ -573,7 +622,7 @@ class SaleOrder(models.Model): def write(self, vals): res = super(SaleOrder, self).write(vals) - + # self._compute_etrts_date() if 'carrier_id' in vals: for picking in self.picking_ids: if picking.state == 'assigned': @@ -1009,6 +1058,7 @@ class SaleOrder(models.Model): order._set_sppkp_npwp_contact() order.calculate_line_no() order.send_notif_to_salesperson() + order._compute_etrts_date() # order.order_line.get_reserved_from() res = super(SaleOrder, self).action_confirm() @@ -1383,13 +1433,14 @@ class SaleOrder(models.Model): def create(self, vals): # Ensure partner details are updated when a sale order is created order = super(SaleOrder, self).create(vals) + order._compute_etrts_date() # order._update_partner_details() return order def write(self, vals): # Call the super method to handle the write operation res = super(SaleOrder, self).write(vals) - + # self._compute_etrts_date() # Check if the update is coming from a save operation # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']): # self._update_partner_details() @@ -1424,4 +1475,6 @@ class SaleOrder(models.Model): raise UserError( "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") res = super(SaleOrder, self).write(vals) + if 'order_line' in vals: + self._compute_etrts_date() return res \ No newline at end of file diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6967e1a3..f766dc3f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1,7 +1,7 @@ from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools.float_utils import float_is_zero -from datetime import timedelta, datetime +from datetime import timedelta, datetime as waktu from itertools import groupby import pytz, requests, json, requests from dateutil import parser @@ -169,6 +169,33 @@ class StockPicking(models.Model): 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") + estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, store=True, related='sale_id.estimated_ready_ship_date') + countdown_hours = fields.Float(string='Countdown in Hours', compute='_compute_countdown_hours', store=True, default=False) + countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_compute_countdown_ready_to_ship') + + @api.depends('estimated_ready_ship_date', 'state') + def _compute_countdown_hours(self): + for record in self: + if record.state in ('cancel', 'done') or not record.estimated_ready_ship_date: + # Gunakan nilai yang sangat besar sebagai placeholder + record.countdown_hours = 999999 + else: + delta = record.estimated_ready_ship_date - waktu.now() + record.countdown_hours = delta.total_seconds() / 3600 + + @api.depends('estimated_ready_ship_date', 'state') + def _compute_countdown_ready_to_ship(self): + for record in self: + if record.state in ('cancel', 'done'): + record.countdown_ready_to_ship = False + else: + if record.estimated_ready_ship_date: + delta = record.estimated_ready_ship_date - waktu.now() + days = delta.days + hours, remainder = divmod(delta.seconds, 3600) + record.countdown_ready_to_ship = f"{days} days, {hours} hours" + else: + record.countdown_ready_to_ship = False def _compute_lalamove_image_html(self): for record in self: diff --git a/indoteknik_custom/views/product_product.xml b/indoteknik_custom/views/product_product.xml index 71748e44..b214dc87 100644 --- a/indoteknik_custom/views/product_product.xml +++ b/indoteknik_custom/views/product_product.xml @@ -31,6 +31,14 @@ model.action_sync_to_solr() + + Generate Product SLA + + + code + model.generate_product_sla() + + Sync Variant To Solr: Solr Flag 2 diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 008a04ed..09a71912 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -69,6 +69,7 @@ + diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 8acba608..ae6ae940 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -7,7 +7,8 @@ - create_date desc + countdown_hours asc + @@ -18,6 +19,8 @@ + + -- cgit v1.2.3 From d0647f4b4a0df94c7b51852823df37eeb5b89e3e Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 3 Feb 2025 14:45:18 +0700 Subject: add generate vendor sla --- indoteknik_custom/models/vendor_sla.py | 75 ++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/vendor_sla.py b/indoteknik_custom/models/vendor_sla.py index 9af86a14..67b6ffc3 100644 --- a/indoteknik_custom/models/vendor_sla.py +++ b/indoteknik_custom/models/vendor_sla.py @@ -1,4 +1,7 @@ from odoo import models, fields, api +import logging +import math +_logger = logging.getLogger(__name__) class VendorSLA(models.Model): _name = 'vendor.sla' @@ -12,15 +15,81 @@ class VendorSLA(models.Model): string="SLA Time" ) duration_unit = fields.Char(string="Duration (Unit)", compute="_compute_duration_unit") - - + + # pertama, lakukan group by vendor pada modul purchase.order + # kedua, pada setiap Purchase order pada group by vendor tersebut, lakukan penghitungan penjumlahan setiap nilai datetime field date_planed dikurangi date_approve purchase order + # dibagi jumlah data dari setiap Purchase order pada group by vendor tersebut. hasilnya lalu di gunakan untuk mengset nilai duration + def generate_vendor_id_sla(self): + # Step 1: Group purchase orders by vendor (partner_id) + po_env = self.env['purchase.order'] + pos = po_env.read_group( + domain=[('state', '=', 'done')], + fields=['partner_id', 'date_planned', 'date_approve'], + groupby=['partner_id'], + lazy=False + ) + + for group in pos: + partner_id = group['partner_id'][0] + total_duration = 0 + count = 0 + + # Step 2: Calculate the average duration for each vendor + pos_for_vendor = po_env.search([ + ('partner_id', '=', partner_id), + ('state', '=', 'done'), + ('date_planned', '>=', '2023-01-01') + ]) + + for po in pos_for_vendor: + if po.date_planned and po.date_approve: + date_planned = fields.Datetime.to_datetime(po.date_planned) + date_approve = fields.Datetime.to_datetime(po.date_approve) + if date_planned < date_approve: continue + duration = (date_planned - date_approve).total_seconds() / 3600 # Convert to hours + total_duration += duration + count += 1 + + if count > 0: + average_duration = total_duration / count + + # Step 3: Update the duration field in the corresponding res.partner record + vendor_sla = self.search([('id_vendor', '=', partner_id)], limit=1) + + # Konversi jam ke hari jika diperlukan + if average_duration >= 24: + days = average_duration / 24 + if days - int(days) > 0.5: # Jika sisa lebih dari 0,5, bulatkan ke atas + days = int(days) + 1 + else: # Jika sisa 0,5 atau kurang, bulatkan ke bawah + days = int(days) + duration_to_save = days + unit_to_save = 'hari' + else: + duration_to_save = round(average_duration) + unit_to_save = 'jam' + + # Update atau create vendor SLA record + if vendor_sla: + vendor_sla.write({ + 'duration': duration_to_save, + 'unit': unit_to_save + }) + else: + self.create({ + 'id_vendor': partner_id, + 'duration': duration_to_save, + 'unit': unit_to_save + }) + _logger.info(f'Proses SLA untuk Vendor selesai dilakukan') + @api.depends('duration', 'unit') def _compute_duration_unit(self): for record in self: if record.duration and record.unit: record.duration_unit = f"{record.duration} {record.unit}" else: - record.duration_unit = "" + record.duration_unit = "-" \ No newline at end of file -- cgit v1.2.3 From dd0158651c5fa665cde6c534e7f4283f86adafc9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 3 Feb 2025 14:46:38 +0700 Subject: add type on barcoding product --- indoteknik_custom/models/barcoding_product.py | 8 ++++++++ indoteknik_custom/views/barcoding_product.xml | 7 +++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index 6bbf9fde..e1b8f41f 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -12,6 +12,14 @@ class BarcodingProduct(models.Model): barcoding_product_line = fields.One2many('barcoding.product.line', 'barcoding_product_id', string='Barcoding Product Lines', auto_join=True) product_id = fields.Many2one('product.product', string="Product", tracking=3) quantity = fields.Float(string="Quantity", tracking=3) + type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product')], string='Type', default='print') + barcode = fields.Char(string="Barcode") + + @api.constrains('barcode') + def _send_barcode_to_product(self): + for record in self: + if record.barcode and not record.product_id.barcode: + record.product_id.barcode = record.barcode @api.onchange('product_id', 'quantity') def _onchange_product_or_quantity(self): diff --git a/indoteknik_custom/views/barcoding_product.xml b/indoteknik_custom/views/barcoding_product.xml index 566655ff..c7473d39 100644 --- a/indoteknik_custom/views/barcoding_product.xml +++ b/indoteknik_custom/views/barcoding_product.xml @@ -8,6 +8,7 @@ + @@ -32,11 +33,13 @@ - + + + - + -- cgit v1.2.3 From 8cf663b15d39a04df97b3ef13f76b407ca6b7004 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Tue, 4 Feb 2025 08:55:01 +0700 Subject: xoretax xml add code barang --- indoteknik_custom/models/coretax_fatur.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index ae6dd2ae..706a4f44 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -77,7 +77,7 @@ class CoretaxFaktur(models.Model): otherTaxBase = round(line.price_subtotal * (11/12)) if line.price_subtotal else 0 good_service = ET.SubElement(list_of_good_service, 'GoodService') ET.SubElement(good_service, 'Opt').text = 'A' - ET.SubElement(good_service, 'Code') + ET.SubElement(good_service, 'Code').text = '000000' ET.SubElement(good_service, 'Name').text = line.name ET.SubElement(good_service, 'Unit').text = 'UM.0018' ET.SubElement(good_service, 'Price').text = str(round(line.price_subtotal/line.quantity, 2)) if line.price_subtotal else '0' -- cgit v1.2.3 From 0bf3a1d1db07ee8306d81443cc4dba94b3740808 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 4 Feb 2025 15:35:41 +0700 Subject: update code sale order --- indoteknik_custom/models/sale_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 132aa397..4bdd9f2c 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -980,6 +980,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: + order.check_credit_limit() if self.validate_different_vendor() and not self.vendor_approval: return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor') -- cgit v1.2.3 From 88834d1fcda0914867fc6420ba8cb9b4046b3a11 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Wed, 5 Feb 2025 16:38:59 +0700 Subject: add public holiday --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/public_holiday.py | 11 +++++ indoteknik_custom/models/sale_order.py | 3 +- indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/public_holiday.xml | 57 ++++++++++++++++++++++++++ 6 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 indoteknik_custom/models/public_holiday.py create mode 100644 indoteknik_custom/views/public_holiday.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index c1593c9e..580f43de 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -159,6 +159,7 @@ 'report/report_sale_order.xml', 'views/vendor_sla.xml', 'views/coretax_faktur.xml', + 'views/public_holiday.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 43fbf146..4c1ef68c 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -140,3 +140,4 @@ from . import va_multi_reject from . import vendor_sla from . import stock_immediate_transfer from . import coretax_fatur +from . import public_holiday diff --git a/indoteknik_custom/models/public_holiday.py b/indoteknik_custom/models/public_holiday.py new file mode 100644 index 00000000..70b7c53a --- /dev/null +++ b/indoteknik_custom/models/public_holiday.py @@ -0,0 +1,11 @@ +from odoo import api, fields, models +from datetime import timedelta, datetime + +class PublicHoliday(models.Model): + _name = 'hr.public.holiday' + _description = 'Public Holidays' + + name = fields.Char(string='Holiday Name', required=True) + start_date = fields.Date('Start Holiday Date', required=True) + end_date = fields.Date('End Holiday Date', required=True) + # company_id = fields.Many2one('res.company', 'Company') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 32e6f11f..7eb4151a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -387,8 +387,7 @@ class SaleOrder(models.Model): else: rec.eta_date = False - def _compute_etrts_date(self): - max_slatime = 100 + max_slatime = 0 # untuk setiap produk dalam so di ambil sla nya sla paling kecil itulah yang dipakai untuk tambah ke max_leadtime = 0 diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index b6212f1b..1f3835fa 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -157,3 +157,4 @@ access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pen 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 access_confirm_approval_wizard,confirm.approval.wizard,model_confirm_approval_wizard,,1,1,1,0 +access_hr_public_holiday,confirm.hr.public.holiday,model_hr_public_holiday,,1,1,1,0 diff --git a/indoteknik_custom/views/public_holiday.xml b/indoteknik_custom/views/public_holiday.xml new file mode 100644 index 00000000..d0b9c5d5 --- /dev/null +++ b/indoteknik_custom/views/public_holiday.xml @@ -0,0 +1,57 @@ + + + + + + hr.public.holiday access + + + + + + + + + + + hr.public.holiday.form + hr.public.holiday + +
+ + + + + + + + +
+
+ + + hr.public.holiday.tree + hr.public.holiday + + + + + + + + + + Public Holidays + hr.public.holiday + tree,form + + + +
+
-- cgit v1.2.3 From 43d859b6c51697a8cff9f543154934ea045d1378 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 6 Feb 2025 10:06:40 +0700 Subject: ubah logic warning limit --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index 29cf391c..b6103bbb 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -598,8 +598,8 @@ class UserPengajuanTempoRequest(models.Model): # user.send_company_request_approve_mail() self.user_company_id.property_payment_term_id = self.tempo_duration.id self.user_company_id.active_limit = True - self.user_company_id.warning_stage = float(limit_tempo) - (float(limit_tempo)/2) self.user_company_id.blocking_stage = limit_tempo + self.user_company_id.warning_stage = float(limit_tempo) - (float(limit_tempo)/2) # Internal Notes comment = [] -- cgit v1.2.3 From f78fcf1c60160540221e240338acc54dfc9beb74 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 6 Feb 2025 11:22:06 +0700 Subject: add po count on po and add validation duplicate contact --- indoteknik_custom/models/purchase_order.py | 1 + indoteknik_custom/models/res_partner.py | 13 +++++++++++++ indoteknik_custom/views/purchase_order.xml | 3 +++ 3 files changed, 17 insertions(+) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 12a94730..54d771ba 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -86,6 +86,7 @@ class PurchaseOrder(models.Model): total_cost_service = fields.Float(string='Total Cost Service') total_delivery_amt = fields.Float(string='Total Delivery Amt') store_name = fields.Char(string='Nama Toko') + purchase_order_count = fields.Integer('Purchase Order Count', related='partner_id.purchase_order_count') @api.onchange('total_cost_service') def _onchange_total_cost_service(self): diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 5322515a..3c5e0e05 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -183,6 +183,19 @@ class ResPartner(models.Model): # # raise UserError('You name it') # return res + + @api.constrains('name') + def _check_duplicate_name(self): + for record in self: + if record.name: + # Mencari partner lain yang memiliki nama sama (case-insensitive) + existing_partner = self.env['res.partner'].search([ + ('id', '!=', record.id), # Hindari mencocokkan diri sendiri + ('name', 'ilike', record.name) # Case-insensitive search + ], limit=1) + + if existing_partner: + raise ValidationError(f"Nama '{record.name}' sudah digunakan oleh partner lain!") def write(self, vals): # Fungsi rekursif untuk meng-update semua child, termasuk child dari child diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index 022937f4..f7f38686 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -51,6 +51,9 @@ + + + -- cgit v1.2.3 From 11ef44bdb2695125048fe7fcfea25dbf459a3d9e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 6 Feb 2025 13:14:25 +0700 Subject: add levenshtein to duplikat name contact alert --- indoteknik_custom/models/res_partner.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 3c5e0e05..844d47f9 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -188,14 +188,17 @@ class ResPartner(models.Model): def _check_duplicate_name(self): for record in self: if record.name: - # Mencari partner lain yang memiliki nama sama (case-insensitive) - existing_partner = self.env['res.partner'].search([ - ('id', '!=', record.id), # Hindari mencocokkan diri sendiri - ('name', 'ilike', record.name) # Case-insensitive search - ], limit=1) - - if existing_partner: - raise ValidationError(f"Nama '{record.name}' sudah digunakan oleh partner lain!") + query = """ + SELECT name FROM res_partner + WHERE id != %s + AND levenshtein(lower(name), lower(%s)) <= 1 + LIMIT 1 + """ + self.env.cr.execute(query, (record.id, record.name)) + duplicate = self.env.cr.fetchone() + + if duplicate: + raise ValidationError(f"Nama '{record.name}' mirip dengan '{duplicate[0]}', harap gunakan nama yang unik!") def write(self, vals): # Fungsi rekursif untuk meng-update semua child, termasuk child dari child -- cgit v1.2.3 From 1abbebda5ab707b014e8e01a7ab418861beef420 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 6 Feb 2025 16:40:34 +0700 Subject: cr validation duplicate contact --- indoteknik_custom/models/res_partner.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 844d47f9..7e574a72 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -188,17 +188,14 @@ class ResPartner(models.Model): def _check_duplicate_name(self): for record in self: if record.name: - query = """ - SELECT name FROM res_partner - WHERE id != %s - AND levenshtein(lower(name), lower(%s)) <= 1 - LIMIT 1 - """ - self.env.cr.execute(query, (record.id, record.name)) - duplicate = self.env.cr.fetchone() - - if duplicate: - raise ValidationError(f"Nama '{record.name}' mirip dengan '{duplicate[0]}', harap gunakan nama yang unik!") + # Mencari partner lain yang memiliki nama sama (case-insensitive) + existing_partner = self.env['res.partner'].search([ + ('id', '!=', record.id), # Hindari mencocokkan diri sendiri + ('name', '=', record.name) # Case-insensitive search + ], limit=1) + + if existing_partner: + raise ValidationError(f"Nama '{record.name}' sudah digunakan oleh partner lain!") def write(self, vals): # Fungsi rekursif untuk meng-update semua child, termasuk child dari child -- cgit v1.2.3 From 62c558bf0f97121f804762780374f44adfb0406e Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 7 Feb 2025 09:36:38 +0700 Subject: add query search similar bussiness --- indoteknik_api/controllers/api_v1/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indoteknik_api/controllers/api_v1/user.py b/indoteknik_api/controllers/api_v1/user.py index f71af89f..1d26d356 100644 --- a/indoteknik_api/controllers/api_v1/user.py +++ b/indoteknik_api/controllers/api_v1/user.py @@ -171,7 +171,8 @@ class User(controller.Controller): query = """ SELECT id, name, levenshtein(name::text, %s) AS distance FROM res_partner - WHERE levenshtein(name::text, %s) < 3 + WHERE is_company = true AND active = true + AND levenshtein(name::text, %s) < 3 ORDER BY distance ASC """ params = (business_name, business_name) -- cgit v1.2.3 From 3187466a66abb41931e346e7865dfa6432f3da9e Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 7 Feb 2025 14:24:02 +0700 Subject: add chek kredit limit tapi ditambah dengan so yang to invoice --- indoteknik_custom/models/sale_order.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4bdd9f2c..0d2e42cb 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -768,6 +768,7 @@ class SaleOrder(models.Model): def sale_order_approve(self): self.check_credit_limit() + self.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor') self.check_due() @@ -826,6 +827,7 @@ class SaleOrder(models.Model): return self._create_approval_notification('Pimpinan') elif order._requires_approval_margin_manager(): self.check_credit_limit() + self.check_limit_so_to_invoice() order.approval_status = 'pengajuan1' return self._create_approval_notification('Sales Manager') @@ -932,6 +934,28 @@ class SaleOrder(models.Model): raise UserError(_("%s is in Blocking Stage, Remaining credit limit is %s, from %s and outstanding %s") % (rec.partner_id.name, remaining_credit_limit, block_stage, outstanding_amount)) + def check_limit_so_to_invoice(self): + for rec in self: + # Ambil jumlah outstanding_amount dan rec.amount_total sebagai current_total + outstanding_amount = rec.outstanding_amount + current_total = rec.amount_total + outstanding_amount + + # Ambil blocking stage dari partner + block_stage = rec.partner_id.parent_id.blocking_stage if rec.partner_id.parent_id else rec.partner_id.blocking_stage or 0 + + # Ambil jumlah nilai dari SO yang invoice_status masih 'to invoice' + so_to_invoice = 0 + for sale in rec.partner_id.sale_order_ids: + if sale.invoice_status == 'to invoice': + so_to_invoice = so_to_invoice + sale.amount_total + # Hitung remaining credit limit + remaining_credit_limit = block_stage - current_total - so_to_invoice + + # Validasi limit + if remaining_credit_limit <= 0: + raise UserError(_("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.") + % (rec.partner_id.name, block_stage - current_total, block_stage, outstanding_amount)) + def validate_different_vendor(self): if self.vendor_approval_id.filtered(lambda v: v.state == 'draft'): draft_names = ", ".join(self.vendor_approval_id.filtered(lambda v: v.state == 'draft').mapped('number')) @@ -981,6 +1005,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: order.check_credit_limit() + order.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor') -- cgit v1.2.3 From ae66937628c5431274df26183674cad52f90c029 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 7 Feb 2025 14:57:43 +0700 Subject: disallow some state while cancel sales order --- indoteknik_custom/models/sale_order.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 0d2e42cb..2f3871ad 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1067,9 +1067,14 @@ class SaleOrder(models.Model): if self.have_outstanding_invoice: raise UserError("Invoice harus di Cancel dahulu") + + disallow_states = ['draft', 'waiting', 'confirmed', 'assigned'] + for picking in self.picking_ids: + if picking.state in disallow_states: + raise UserError("DO yang draft, waiting, confirmed, atau assigned harus di-cancel oleh Logistik") for line in self.order_line: if line.qty_delivered > 0: - raise UserError("DO harus di-cancel terlebih dahulu.") + raise UserError("DO yang done harus di-Return oleh Logistik") if not self.web_approval: self.web_approval = 'company' -- cgit v1.2.3 From 0dbcd5eb060924a0860b2586776f65d5ce19b9ef Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Fri, 7 Feb 2025 15:07:01 +0700 Subject: estimation delerivery date --- indoteknik_api/controllers/api_v1/product.py | 18 ++++++- indoteknik_custom/models/sale_order.py | 77 +++++++++++++--------------- indoteknik_custom/views/sale_order.xml | 2 +- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py index 5d564ff9..93ef305c 100644 --- a/indoteknik_api/controllers/api_v1/product.py +++ b/indoteknik_api/controllers/api_v1/product.py @@ -9,6 +9,17 @@ import json _logger = logging.getLogger(__name__) +def get_days_until_next_business_day(start_date=None, *args, **kwargs): + today = start_date or datetime.today().date() + offset = 0 # Counter jumlah hari yang ditambahkan + + while today.weekday() >= 5 : + today += timedelta(days=1) + offset += 1 + + return offset + + class Product(controller.Controller): prefix = '/api/v1/' @@ -75,12 +86,17 @@ class Product(controller.Controller): if product_sla.sla_vendor_id.unit != 'jam': include_instant = False break + + start_date = datetime.today().date() + additional_days = get_days_until_next_business_day(start_date) # Jika semua loop selesai tanpa include_instant menjadi False return self.response({ 'include_instant': include_instant, 'sla_duration': sla_duration, - 'sla_unit': sla_unit + 'sla_additional_days': additional_days, + 'sla_total' : int(sla_duration) + int(additional_days), + 'sla_unit': 'Hari' if additional_days > 0 else sla_unit } ) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 32e6f11f..cae99447 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -146,10 +146,11 @@ class SaleOrder(models.Model): ]) estimated_ready_ship_date = fields.Datetime( string='ET Ready to Ship', - copy=False, + compute='_compute_etrts_date', store=True + ) - expected_ready_ship_date = fields.Datetime( + expected_ready_to_ship = fields.Datetime( string='ET Ready to Ship FIX', copy=False, store=True @@ -373,39 +374,29 @@ class SaleOrder(models.Model): rec.compute_fullfillment = True + @api.depends('date_order', 'estimated_arrival_days', 'state') def _compute_eta_date(self): - max_leadtime = 0 - - for line in self.order_line: - leadtime = line.vendor_id.leadtime - max_leadtime = max(max_leadtime, leadtime) - - for rec in self: - if rec.date_order and rec.state not in ['cancel', 'draft']: - eta_date = datetime.now() + timedelta(days=max_leadtime) - rec.eta_date = eta_date + for rec in self: + if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days: + rec.eta_date = rec.date_order + timedelta(days=rec.estimated_arrival_days) else: rec.eta_date = False - def _compute_etrts_date(self): - max_slatime = 100 - - # untuk setiap produk dalam so di ambil sla nya sla paling kecil itulah yang dipakai untuk tambah ke - max_leadtime = 0 - - for line in self.order_line: - product_sla = self.env['product.sla'].search([('product_variant_id', '=', line.product_id.id)]) - slatime = int(product_sla.sla) or 1 - max_slatime = max(max_slatime, slatime) - + @api.depends("order_line.product_id") + def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date for rec in self: + max_slatime = 1 # Default SLA jika tidak ada + for line in rec.order_line: + product_sla = self.env['product.sla'].search([('product_variant_id', '=', line.product_id.id)], limit=1) + slatime = int(product_sla.sla) if product_sla and product_sla.sla else 1 + max_slatime = max(max_slatime, slatime) + if rec.date_order: eta_date = datetime.now() + timedelta(days=max_slatime) rec.estimated_ready_ship_date = eta_date - if not rec.expected_ready_ship_date: - rec.expected_ready_ship_date = eta_date - # else: - # rec.estimated_ready_ship_date = False + # Jika expected_ready_to_ship kosong, set nilai default + if not rec.expected_ready_to_ship: + rec.expected_ready_to_ship = eta_date def _set_etrts_date(self): for order in self: @@ -413,19 +404,6 @@ class SaleOrder(models.Model): raise UserError(_("You cannot change the Estimated Ready To Ship Date on a done, sale or cancelled order.")) # order.move_lines.write({'estimated_ready_ship_date': order.estimated_ready_ship_date}) - @api.onchange('estimated_ready_ship_date') - def _onchange_estimated_ready_ship_date(self): - for record in self: - if (record.estimated_ready_ship_date and record.expected_ready_ship_date): - if(record.estimated_ready_ship_date < record.expected_ready_ship_date): - return { - 'warning': { - 'title': _('Requested date is too soon.'), - 'message': _("The delivery date is sooner than the expected date." - "You may be unable to honor the delivery date.") - } - } - def _prepare_invoice(self): """ Prepare the dict of values to create the new invoice for a sales order. This method may be @@ -619,6 +597,21 @@ class SaleOrder(models.Model): if line.product_id.type == 'product': line_no += 1 line.line_no = line_no + + @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship + def _onchange_expected_ready_ship_date(self): + for rec in self: + if rec.expected_ready_to_ship and rec.estimated_ready_ship_date: + # Hanya membandingkan tanggal saja, tanpa jam + expected_date = rec.expected_ready_to_ship.date() + estimated_date = rec.estimated_ready_ship_date.date() + + if expected_date < estimated_date: + rec.expected_ready_to_ship = rec.estimated_ready_ship_date + raise ValidationError( + "Tanggal 'Expected Ready to Ship' tidak boleh lebih kecil dari {}. Mohon pilih tanggal minimal {}." + .format(estimated_date.strftime('%d-%m-%Y'), estimated_date.strftime('%d-%m-%Y')) + ) def write(self, vals): res = super(SaleOrder, self).write(vals) @@ -627,7 +620,7 @@ class SaleOrder(models.Model): for picking in self.picking_ids: if picking.state == 'assigned': picking.carrier_id = self.carrier_id - + return res def calculate_so_status(self): @@ -1433,7 +1426,7 @@ class SaleOrder(models.Model): def create(self, vals): # Ensure partner details are updated when a sale order is created order = super(SaleOrder, self).create(vals) - order._compute_etrts_date() + # order._compute_etrts_date() # order._update_partner_details() return order diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 09a71912..74fc6e54 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -69,7 +69,7 @@ - + -- cgit v1.2.3 From f4ceb91897a1a3ababd5097d59064760e3ebb8b9 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 7 Feb 2025 16:10:27 +0700 Subject: fix hidden button reject tempo --- indoteknik_custom/views/user_pengajuan_tempo_request.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/indoteknik_custom/views/user_pengajuan_tempo_request.xml b/indoteknik_custom/views/user_pengajuan_tempo_request.xml index bb8262c9..b6a6206e 100644 --- a/indoteknik_custom/views/user_pengajuan_tempo_request.xml +++ b/indoteknik_custom/views/user_pengajuan_tempo_request.xml @@ -30,7 +30,6 @@ string="Reject" attrs="{'invisible': [('state_tempo', 'in', ['approval_director','reject'])]}" type="object" - groups="purchase.group_purchase_manager" class="oe_highlight"/> Date: Mon, 10 Feb 2025 09:44:05 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cc86c451..ec761900 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1217,4 +1217,19 @@ 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' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + product_id = fields.(string='Koli') \ No newline at end of file -- cgit v1.2.3 From e960a38aa69fbd7a75b6f06a3b30967d8b20ddd5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 10 Feb 2025 09:45:03 +0700 Subject: fix bug create payment link --- indoteknik_custom/models/sale_order.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 2f3871ad..5c223beb 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -549,22 +549,22 @@ class SaleOrder(models.Model): redirect_url = json.loads(lookup_json)['redirect_url'] self.payment_link_midtrans = str(redirect_url) - # Generate QR code - qr = qrcode.QRCode( - version=1, - error_correction=qrcode.constants.ERROR_CORRECT_L, - box_size=10, - border=4, - ) - qr.add_data(redirect_url) - qr.make(fit=True) - img = qr.make_image(fill_color="black", back_color="white") - - buffer = BytesIO() - img.save(buffer, format="PNG") - qr_code_img = base64.b64encode(buffer.getvalue()).decode() - - self.payment_qr_code = qr_code_img + if 'redirect_url' in response: + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data(redirect_url) + qr.make(fit=True) + img = qr.make_image(fill_color="black", back_color="white") + + buffer = BytesIO() + img.save(buffer, format="PNG") + qr_code_img = base64.b64encode(buffer.getvalue()).decode() + + self.payment_qr_code = qr_code_img @api.model def _generate_so_access_token(self, limit=50): -- cgit v1.2.3 From 3d5e57a2d892551965bcf079f7c728d00989af8e Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 10 Feb 2025 14:05:12 +0700 Subject: update make contak from tempo --- .../models/user_pengajuan_tempo_request.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index b6103bbb..81509df4 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -507,10 +507,20 @@ class UserPengajuanTempoRequest(models.Model): # Buat kontak baru untuk company_id for contact_data in contacts_data: - self.env['res.partner'].create({ - "parent_id": self.user_company_id.id, # Hubungkan ke perusahaan - **contact_data, # Tambahkan data kontak - }) + existing_contact = self.env['res.partner'].search([ + ('parent_id', '=', self.user_company_id.id), + ('name', '=', contact_data['name']) + ], limit=1) + + if existing_contact: + # Perbarui data yang ada + existing_contact.write(contact_data) + else: + # Buat kontak baru jika belum ada + self.env['res.partner'].create({ + "parent_id": self.user_company_id.id, # Hubungkan ke perusahaan + **contact_data, # Tambahkan data kontak + }) # Pengiriman self.user_company_id.pic_name = self.pengajuan_tempo_id.pic_name -- cgit v1.2.3 From 8a29d6ef46d15fbce033b94464e139cabb8aeb68 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 10 Feb 2025 14:23:02 +0700 Subject: update yg sama --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index 81509df4..3f960e58 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -513,8 +513,9 @@ class UserPengajuanTempoRequest(models.Model): ], limit=1) if existing_contact: - # Perbarui data yang ada - existing_contact.write(contact_data) + # Perbarui hanya field yang tidak menyebabkan konflik + update_data = {k: v for k, v in contact_data.items() if k != 'name'} + existing_contact.write(update_data) else: # Buat kontak baru jika belum ada self.env['res.partner'].create({ -- cgit v1.2.3 From 1ff643c37772d573b5bf4c1814c85cf442de1863 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 10 Feb 2025 15:05:14 +0700 Subject: update logic add contact tempo(revisi) --- .../models/user_pengajuan_tempo_request.py | 25 ++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index 3f960e58..0b77cc87 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -513,11 +513,28 @@ class UserPengajuanTempoRequest(models.Model): ], limit=1) if existing_contact: - # Perbarui hanya field yang tidak menyebabkan konflik - update_data = {k: v for k, v in contact_data.items() if k != 'name'} - existing_contact.write(update_data) + # Pastikan tidak ada duplikasi nama dalam perusahaan yang sama + duplicate_check = self.env['res.partner'].search([ + ('name', '=', contact_data['name']), + ('id', '!=', existing_contact.id) # Hindari update yang menyebabkan duplikasi global + ], limit=1) + + if not duplicate_check: + # Perbarui hanya field yang tidak menyebabkan konflik + update_data = {k: v for k, v in contact_data.items() if k != 'name'} + existing_contact.write(update_data) + else: + raise UserError(f"Skipping update for {contact_data['name']} due to existing duplicate.") else: - # Buat kontak baru jika belum ada + # Pastikan tidak ada partner lain dengan nama yang sama sebelum membuat baru + duplicate_check = self.env['res.partner'].search([ + ('name', '=', contact_data['name']) + ], limit=1) + + if duplicate_check: + # Jika nama sudah ada tetapi di perusahaan lain, tambahkan nama perusahaan + contact_data['name'] = f"{contact_data['name']} ({self.user_company_id.name})" + self.env['res.partner'].create({ "parent_id": self.user_company_id.id, # Hubungkan ke perusahaan **contact_data, # Tambahkan data kontak -- cgit v1.2.3 From 84c259bb2c579a0ad1f1593d7d3d7bf57fb732f0 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 10 Feb 2025 16:55:44 +0700 Subject: add tracking reason reject tempo --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index 0b77cc87..e2c08cb2 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -7,7 +7,7 @@ class RejectReasonWizard(models.TransientModel): _description = 'Wizard for Reject Reason' request_id = fields.Many2one('user.pengajuan.tempo.request', string='Request') - reason_reject = fields.Text(string='Reason for Rejection', required=True) + reason_reject = fields.Text(string='Reason for Rejection', required=True, tracking=True) def confirm_reject(self): tempo = self.request_id -- cgit v1.2.3 From e7eadc269299d03f6eb0702459147a2900b982b4 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 11 Feb 2025 09:03:25 +0700 Subject: update code blocking state --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 5c223beb..9631fe6e 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -952,7 +952,7 @@ class SaleOrder(models.Model): remaining_credit_limit = block_stage - current_total - so_to_invoice # Validasi limit - if remaining_credit_limit <= 0: + if remaining_credit_limit <= 0 and block_stage > 0: raise UserError(_("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.") % (rec.partner_id.name, block_stage - current_total, block_stage, outstanding_amount)) -- cgit v1.2.3 From c99bf4c49859450ce4cb081c920edda2077b3b1a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 11 Feb 2025 09:48:55 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 34 +++++++++++++++++++++++++- indoteknik_custom/security/ir.model.access.csv | 2 ++ indoteknik_custom/views/stock_picking.xml | 28 +++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ec761900..3f888b02 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -16,6 +16,8 @@ _logger = logging.getLogger(__name__) class StockPicking(models.Model): _inherit = 'stock.picking' + 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) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') @@ -122,6 +124,8 @@ class StockPicking(models.Model): ('cancel', 'Cancelled'), ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") notee = fields.Text(string="Note") + quantity_koli = fields.Float(string="Quantity Koli") + source_koli_id = fields.Many2one('stock.picking', string="Source Koli") @api.model def _compute_dokumen_tanda_terima(self): @@ -166,6 +170,14 @@ 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") + @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 _compute_lalamove_image_html(self): for record in self: if record.lalamove_image_url: @@ -1223,6 +1235,7 @@ class CheckKoli(models.Model): _name = 'check.koli' _description = 'Check Koli' _order = 'picking_id, id' + _rec_name = 'koli' picking_id = fields.Many2one( 'stock.picking', @@ -1232,4 +1245,23 @@ class CheckKoli(models.Model): index=True, copy=False, ) - product_id = fields.(string='Koli') \ No newline at end of file + koli = fields.Char(string='Koli') + +class ScanKoli(models.Model): + _name = 'scan.koli' + _description = 'Scan Koli' + _order = 'picking_id, id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + koli_id = fields.Many2one('check.koli', string='Koli') + + @api.constrains('koli_id') + def _constrains_koli_id(self): + self.picking_id.source_koli_id = self.koli_id.picking_id.id \ 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 73877052..fa126492 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -150,6 +150,8 @@ access_v_move_outstanding,access.v.move.outstanding,model_v_move_outstanding,,1, 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_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_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 diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 1c6ae8f7..42fa481d 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -72,6 +72,8 @@ + + @@ -198,11 +200,37 @@ + + + + + + + + scan.koli.tree + scan.koli + + + + + + + + + check.koli.tree + check.koli + + + + + + + check.product.tree check.product -- cgit v1.2.3 From 597e3b3f62d492f0bb113138decc130477f89ed6 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 11 Feb 2025 10:27:32 +0700 Subject: hide date_planned --- indoteknik_custom/views/purchase_order.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index f7f38686..3e4dd89c 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -81,6 +81,9 @@ {'no_create': True} + + 1 + -- cgit v1.2.3 From 751645803b13cbc96d4a554a9c9d8a63cd991486 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 11 Feb 2025 13:45:07 +0700 Subject: fix bug state reserve --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ce198be3..b1b1bdb8 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -467,19 +467,19 @@ class StockPicking(models.Model): def check_state_reserve(self): pickings = self.search([ - ('state', 'not in', ['cancel', 'done']), + ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'outgoing') ]) for picking in pickings: - fullfillments = self.env['sales.order.fullfillment'].search([ - ('sales_order_id', '=', picking.sale_id.id) + fullfillments = self.env['sales.order.fulfillment.v2'].search([ + ('sale_order_id', '=', picking.sale_id.id) ]) picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - if any(rec.reserved_from not in ['Inventory On Hand', 'Reserved from stock', 'Free Stock'] for rec in fullfillments): + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' -- cgit v1.2.3 From ede74761c8556b18b4555af22738a76538682512 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 11 Feb 2025 14:52:22 +0700 Subject: add CR cancel so >30jt --- indoteknik_custom/models/sale_order.py | 73 +++++++++++++++++++++++++- indoteknik_custom/security/ir.model.access.csv | 3 +- indoteknik_custom/views/sale_order.xml | 25 +++++++++ 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 9631fe6e..b1039750 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -7,6 +7,44 @@ from collections import defaultdict _logger = logging.getLogger(__name__) +class CancelReasonOrder(models.TransientModel): + _name = 'cancel.reason.order' + _description = 'Wizard for Cancel Reason order' + + request_id = fields.Many2one('sale.order', string='Request') + reason_cancel = fields.Selection([ + ('harga_terlalu_mahal', 'Harga barang terlalu mahal'), + ('harga_web_tidak_valid', 'Harga web tidak valid'), + ('stok_kosong', 'Stock kosong'), + ('tidak_mau_indent', 'Customer tidak mau indent'), + ('batal_rencana_pembelian', 'Customer membatalkan rencana pembelian'), + ('vendor_tidak_support_demo', 'Vendor tidak support demo/trial product'), + ('product_knowledge_kurang', 'Product knowledge kurang baik'), + ('barang_tidak_sesuai', 'Barang tidak sesuai/tepat'), + ('tidak_sepakat_pembayaran', 'Tidak menemukan kesepakatan untuk pembayaran'), + ('dokumen_tidak_support', 'Indoteknik tidak bisa support document yang dibutuhkan (Ex: TKDN, COO, SNI)'), + ('ganti_quotation', 'Ganti Quotation'), + ('testing_internal', 'Testing Internal'), + ], string='Reason for Cancel', required=True, copy=False, index=True, tracking=3) + attachment_bukti = fields.Many2many( + 'ir.attachment', + string="Attachment Bukti", readonly=False, + tracking=3, required=True + ) + nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) + + def confirm_reject(self): + order = self.request_id + if order: + order.write({'reason_cancel': self.reason_cancel}) + order.write({'attachment_bukti': self.attachment_bukti}) + order.message_post(body='Attachment Bukti Cancel', + attachment_ids=[self.attachment_bukti.id]) + if self.nomor_so_pengganti or self.reason_cancel == 'ganti_quotation': + order.write({'nomor_so_pengganti': self.nomor_so_pengganti}) + order.confirm_cancel_order() + + return {'type': 'ir.actions.act_window_close'} class SaleOrder(models.Model): _inherit = "sale.order" @@ -145,6 +183,25 @@ class SaleOrder(models.Model): ('NP', 'Non Pareto') ]) shipping_method_picking = fields.Char(string='Shipping Method Picking', compute='_compute_shipping_method_picking') + reason_cancel = fields.Selection([ + ('harga_terlalu_mahal', 'Harga barang terlalu mahal'), + ('harga_web_tidak_valid', 'Harga web tidak valid'), + ('stok_kosong', 'Stock kosong'), + ('tidak_mau_indent', 'Customer tidak mau indent'), + ('batal_rencana_pembelian', 'Customer membatalkan rencana pembelian'), + ('vendor_tidak_support_demo', 'Vendor tidak support demo/trial product'), + ('product_knowledge_kurang', 'Product knowledge kurang baik'), + ('barang_tidak_sesuai', 'Barang tidak sesuai/tepat'), + ('tidak_sepakat_pembayaran', 'Tidak menemukan kesepakatan untuk pembayaran'), + ('dokumen_tidak_support', 'Indoteknik tidak bisa support document yang dibutuhkan (Ex: TKDN, COO, SNI)'), + ('ganti_quotation', 'Ganti Quotation'), + ('testing_internal', 'Testing Internal'), + ], string='Reason for Cancel', copy=False, index=True, tracking=3) + attachment_bukti = fields.Many2one( + 'ir.attachment', + string="Attachment Bukti Cancel", readonly=False, + ) + nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) def _compute_shipping_method_picking(self): for order in self: @@ -1085,8 +1142,22 @@ class SaleOrder(models.Model): self.due_id = False if main_parent.use_so_approval: self.send_notif_to_salesperson(cancel=True) + for order in self: + if order.amount_total > 30000000: + return { + 'type': 'ir.actions.act_window', + 'name': _('Cancel Reason'), + 'res_model': 'cancel.reason.order', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_request_id': self.id}, + } return super(SaleOrder, self).action_cancel() - + + def confirm_cancel_order(self): + """Fungsi ini akan dipanggil oleh wizard setelah alasan pembatalan dipilih""" + return super(SaleOrder, self).action_cancel() + def validate_partner_invoice_due(self): parent_id = self.partner_id.parent_id.id parent_id = parent_id if parent_id else self.partner_id.id diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index d4b79cd9..6709370f 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -161,4 +161,5 @@ access_reject_reason_wizard,reject.reason.wizard,model_reject_reason_wizard,,1,1 access_confirm_approval_wizard,confirm.approval.wizard,model_confirm_approval_wizard,,1,1,1,0 access_barcode_product,access.barcode.product,model_barcode_product,,1,1,1,1 access_barcoding_product,access.barcoding.product,model_barcoding_product,,1,1,1,1 -access_barcoding_product_line,access.barcoding.product.line,model_barcoding_product_line,,1,1,1,1 \ No newline at end of file +access_barcoding_product_line,access.barcoding.product.line,model_barcoding_product_line,,1,1,1,1 +access_cancel_reason_order,cancel.reason.order,model_cancel_reason_order,,1,1,1,0 diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 2a46901a..daa2b095 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -283,6 +283,31 @@ + + + cancel.reason.order.form + cancel.reason.order + +
+ + + + + +
+
+ +
+
+ + + Cancel Reason + cancel.reason.order + form + new + -- cgit v1.2.3 From eeff963c94f4d933b89308f40b387fd67ef881c4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 11 Feb 2025 16:18:57 +0700 Subject: fix bug backorder state reserve --- indoteknik_custom/models/stock_picking.py | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b1b1bdb8..f49c493c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -470,6 +470,38 @@ class StockPicking(models.Model): ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'outgoing') ]) + + count = self.search_count([ + ('state', 'not in', ['cancel', 'draft', 'done']), + ('picking_type_code', '=', 'outgoing') + ]) + + for picking in pickings: + fullfillments = self.env['sales.order.fulfillment.v2'].search([ + ('sale_order_id', '=', picking.sale_id.id) + ]) + + picking.state_reserve = 'ready' + picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() + + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): + picking.state_reserve = 'waiting' + picking.date_reserved = '' + + self.check_state_reserve_backorder() + + def check_state_reserve_backorder(self): + pickings = self.search([ + ('backorder_id', '!=', False), + ('picking_type_code', '=', 'outgoing'), + ('state', 'not in', ['cancel', 'draft', 'done']) + ]) + + count = self.search_count([ + ('backorder_id', '!=', False), + ('picking_type_code', '=', 'outgoing'), + ('state', 'not in', ['cancel', 'draft', 'done']) + ]) for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ -- cgit v1.2.3 From ee106a0245fe05c3b5341e7c0f2606d7c5adb8de Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 11 Feb 2025 16:40:46 +0700 Subject: add tracking reason reject and reset to draft --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 12 +++++++++++- indoteknik_custom/views/user_pengajuan_tempo_request.xml | 6 +++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index e2c08cb2..4a1994fb 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -55,7 +55,14 @@ class UserPengajuanTempoRequest(models.Model): ('approval_director', 'Approved by Director'), ('reject', 'Rejected'), ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft') - reason_reject = fields.Char(string='Limit Tempo') + last_state_tempo = fields.Selection([ + ('draft', 'Pengajuan Tempo'), + ('approval_sales', 'Approved by Sales Manager'), + ('approval_finance', 'Approved by Finance'), + ('approval_director', 'Approved by Director'), + ('reject', 'Rejected'), + ], string='Status',) + reason_reject = fields.Char(string='Reason Reaject', tracking=True, track_visibility='onchange') # informasi perusahaan name_tempo = fields.Many2one('res.partner', string='Nama Perusahaan', related='pengajuan_tempo_id.name_tempo', store=True, tracking=True, readonly=False) @@ -409,6 +416,9 @@ class UserPengajuanTempoRequest(models.Model): 'target': 'new', 'context': {'default_request_id': self.id}, } + def button_draft(self): + for tempo in self: + tempo.state_tempo = tempo.last_state_tempo if tempo.last_state_tempo else 'draft' def write(self, vals): is_approve = True if self.state_tempo == 'approval_director' or vals.get('state_tempo') == 'approval_director' else False diff --git a/indoteknik_custom/views/user_pengajuan_tempo_request.xml b/indoteknik_custom/views/user_pengajuan_tempo_request.xml index b6a6206e..4f047831 100644 --- a/indoteknik_custom/views/user_pengajuan_tempo_request.xml +++ b/indoteknik_custom/views/user_pengajuan_tempo_request.xml @@ -28,7 +28,11 @@ class="oe_highlight"/>
+ @@ -299,6 +300,7 @@ + -- cgit v1.2.3 From 92222d326692652b2c4146e0e6040c74f75d4abc Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Fri, 21 Feb 2025 11:29:14 +0700 Subject: tracking --- indoteknik_api/controllers/api_v1/stock_picking.py | 2 +- indoteknik_custom/models/stock_picking.py | 93 ++++++++++++++++++++-- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index 110cde8a..2fc4d8a5 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -101,7 +101,7 @@ class StockPicking(controller.Controller): picking = picking_model.browse(id) if not picking: return self.response(None) - + hostori = picking.get_tracking_detail() return self.response(picking.get_tracking_detail()) @http.route(prefix + 'stock-picking//tracking', auth='public', method=['GET', 'OPTIONS']) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index e7d9dbd5..49c17788 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -12,8 +12,15 @@ import base64 import requests import time import logging +import re +from deep_translator import GoogleTranslator _logger = logging.getLogger(__name__) +_biteship_url = "https://api.biteship.com/v1" +_biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" + + + class StockPicking(models.Model): _inherit = 'stock.picking' # check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) @@ -363,9 +370,10 @@ class StockPicking(models.Model): raise UserError(f"Kesalahan tidak terduga: {str(e)}") def action_send_to_biteship(self): - url = "https://api.biteship.com/v1/orders" - api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" - + + if self.biteship_tracking_id: + raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}") + # Mencari data sale.order.line berdasarkan sale_id products = self.env['sale.order.line'].search([('order_id', '=', self.sale_id.id)]) @@ -401,6 +409,7 @@ class StockPicking(models.Model): }) payload = { + "reference_id " : self.sale_id.name, "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', "shipper_organization": self.carrier_id.name, @@ -433,14 +442,15 @@ class StockPicking(models.Model): }, "items": items_data_instant }) - + + api_key = _biteship_api_key headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } # Kirim request ke Biteship - response = requests.post(url, headers=headers, json=payload) + response = requests.post(_biteship_url+'/orders', headers=headers, json=payload) if response.status_code == 200: data = response.json() @@ -448,6 +458,7 @@ class StockPicking(models.Model): self.biteship_id = data.get("id", "") self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "") self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "") + self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "") return data else: @@ -1016,8 +1027,19 @@ class StockPicking(models.Model): 'waybill_number': self.delivery_tracking_no or '', 'delivery_status': None, 'eta': self.generate_eta_delivery(), + 'is_biteship': True if self.biteship_id else False, 'manifests': self.get_manifests() } + + if self.biteship_id : + histori = self.get_manifest_biteship() + response['manifests'] = histori.get("manifests", []) + response['delivered'] = histori.get("delivered", False) or self.sj_return_date != False or self.driver_arrival_date != False + response['status'] = self._map_status_biteship(histori.get("delivered")) + + response + + return response if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0: response['delivered'] = self.sj_return_date != False or self.driver_arrival_date != False @@ -1030,6 +1052,67 @@ class StockPicking(models.Model): return response + def get_manifest_biteship(self): + api_key = _biteship_api_key + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + } + + + manifests = [] + + try: + # Kirim request ke Biteship + response = requests.get(_biteship_url+'/trackings/'+self.biteship_tracking_id, headers=headers, json=manifests) + result = response.json() + if(result.get('success') == True): + history = result.get("history", []) + status = result.get("status", "") + + for entry in reversed(history): + manifests.append({ + "status": re.sub(r'[^a-zA-Z0-9\s]', ' ', entry["status"]).lower().capitalize(), + "datetime": self._convert_to_local_time(entry["updated_at"]), + "description": GoogleTranslator(source='auto', target='id').translate(entry["note"]), + }) + + return { + "manifests": manifests, + "delivered": status + } + + return manifests + except Exception as e : + _logger.error(f"Error fetching Biteship order for picking {self.id}: {str(e)}") + return { 'error': str(e) } + + def _convert_to_local_time(self, iso_date): + try: + dt_with_tz = waktu.fromisoformat(iso_date) + utc_dt = dt_with_tz.astimezone(pytz.utc) + + local_tz = pytz.timezone("Asia/Jakarta") + local_dt = utc_dt.astimezone(local_tz) + + return local_dt.strftime("%Y-%m-%d %H:%M:%S") + except Exception as e: + return str(e) + + def _map_status_biteship(self, status): + status_mapping = { + "confirmed": "pending", + "scheduled": "pending", + "allocated": "pending", + "picking_up": "pending", + "picked": "shipment", + "cancelled": "cancelled", + "on_hold": "on_hold", + "dropping_off": "shipment", + "delivered": "completed" + } + return status_mapping.get(status, "Hubungi Admin") + def generate_eta_delivery(self): current_date = datetime.datetime.now() prepare_days = 3 -- cgit v1.2.3 From f0d995cc220cefffe65ce308ee234528ddc0d6ed Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 24 Feb 2025 09:33:28 +0700 Subject: biteship --- indoteknik_api/controllers/api_v1/stock_picking.py | 7 ++++++- indoteknik_custom/models/public_holiday.py | 4 ++-- indoteknik_custom/views/public_holiday.xml | 2 -- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index 2fc4d8a5..3e58417f 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -136,4 +136,9 @@ class StockPicking(controller.Controller): return self.response({ 'name': picking_data.name - }) \ No newline at end of file + }) + + @http.route(prefix + 'n', auth='public', methods=['PUT', 'OPTIONS'], csrf=False) + def udpate_status_from_bitehsip(self, **kw): + picking_code = int(kw.get('picking_code', 0)) + \ No newline at end of file diff --git a/indoteknik_custom/models/public_holiday.py b/indoteknik_custom/models/public_holiday.py index 70b7c53a..851d9080 100644 --- a/indoteknik_custom/models/public_holiday.py +++ b/indoteknik_custom/models/public_holiday.py @@ -6,6 +6,6 @@ class PublicHoliday(models.Model): _description = 'Public Holidays' name = fields.Char(string='Holiday Name', required=True) - start_date = fields.Date('Start Holiday Date', required=True) - end_date = fields.Date('End Holiday Date', required=True) + start_date = fields.Date('Date Holiday', required=True) + # end_date = fields.Date('End Holiday Date', required=True) # company_id = fields.Many2one('res.company', 'Company') diff --git a/indoteknik_custom/views/public_holiday.xml b/indoteknik_custom/views/public_holiday.xml index d0b9c5d5..146c5b0b 100644 --- a/indoteknik_custom/views/public_holiday.xml +++ b/indoteknik_custom/views/public_holiday.xml @@ -22,7 +22,6 @@ - @@ -36,7 +35,6 @@ - -- cgit v1.2.3 From c1810b315d820a184db47d551b39700ce00d1440 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Feb 2025 09:57:56 +0700 Subject: push wms --- indoteknik_custom/models/sales_order_koli.py | 1 + .../models/stock_backorder_confirmation.py | 63 +++---- .../models/stock_immediate_transfer.py | 8 +- indoteknik_custom/models/stock_picking.py | 192 +++++++++------------ indoteknik_custom/views/sale_order.xml | 2 + indoteknik_custom/views/stock_picking.xml | 5 +- 6 files changed, 110 insertions(+), 161 deletions(-) diff --git a/indoteknik_custom/models/sales_order_koli.py b/indoteknik_custom/models/sales_order_koli.py index 02e85256..c782a40e 100644 --- a/indoteknik_custom/models/sales_order_koli.py +++ b/indoteknik_custom/models/sales_order_koli.py @@ -22,4 +22,5 @@ class SalesOrderKoli(models.Model): ) 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 index 0fd7c34e..f4da4cb5 100644 --- a/indoteknik_custom/models/stock_backorder_confirmation.py +++ b/indoteknik_custom/models/stock_backorder_confirmation.py @@ -5,46 +5,29 @@ class StockBackorderConfirmation(models.TransientModel): _inherit = 'stock.backorder.confirmation' def process(self): - res = super(StockBackorderConfirmation, self).process() - pickings_to_do = self.env['stock.picking'] + pickings_not_to_do = self.env['stock.picking'] for line in self.backorder_confirmation_line_ids: - if line.to_backorder: + 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 - - for pick in pickings_to_do: - # Mencari backorder yang baru terbentuk - backorder = self.env['stock.picking'].search([('backorder_id', '=', pick.id)], limit=1) - - if backorder: - # Cari BU/OUT terbaru berdasarkan sale_id - latest_out_picking = self.env['stock.picking'].search([ - ('sale_id', '=', pick.sale_id.id), - ('picking_type_id.code', '=', 'outgoing') - ], order='id desc', limit=1) - - # Update linked_out_picking_id pada backorder BU/PICK - if latest_out_picking: - backorder.linked_out_picking_id = latest_out_picking.id - else: - backorder.linked_out_picking_id = pick.linked_out_picking_id - - # 🚀 Cek apakah ada backorder baru dari BU/OUT - for pick in self.env['stock.picking'].search([ - ('picking_type_id.code', '=', 'outgoing'), - ('backorder_id', '!=', False) - ]): - # Backorder BU/OUT terbaru - latest_out_backorder = self.env['stock.picking'].search([ - ('backorder_id', '=', pick.id) - ], order='id desc', limit=1) - - if latest_out_backorder: - # 🚀 Update semua BU/PICK yang belum `done` atau `cancel` - self.env['stock.picking'].search([ - ('sale_id', '=', pick.sale_id.id), - ('picking_type_id.code', '=', 'incoming'), - ('state', 'not in', ['done', 'cancel']) - ]).write({'linked_out_picking_id': latest_out_backorder.id}) - - return res + 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 4be0dff2..ec00df7b 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -5,25 +5,25 @@ 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: if line.to_immediate is True: + line.picking_id.send_mail_bills() + line.picking_id.send_koli_to_so() pickings_to_do |= line.picking_id else: pickings_not_to_do |= line.picking_id for picking in pickings_to_do: - picking.send_mail_bills() - # If still in draft => confirm and assign if picking.state == 'draft': picking.action_confirm() if picking.state != 'assigned': 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 @@ -33,4 +33,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_picking.py b/indoteknik_custom/models/stock_picking.py index 02ce819f..f3af00d9 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -125,7 +125,6 @@ class StockPicking(models.Model): ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") notee = fields.Text(string="Note") quantity_koli = fields.Float(string="Quantity Koli", copy=False) - source_koli_id = fields.Many2one('stock.picking', string="Source Koli") @api.model def _compute_dokumen_tanda_terima(self): @@ -170,45 +169,17 @@ 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_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli") 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) - backorder_picking_id = fields.Many2one('stock.picking', string="Backorder Picking", copy=False) - - def action_create_backorder(self): - """ Override method to handle backorder logic automatically """ - backorder = super(StockPicking, self).action_create_backorder() - - for picking in self: - if 'BU/PICK/' in picking.name: - # Jika BU/PICK memiliki BU/OUT yang terhubung - if picking.linked_out_picking_id: - out_picking = picking.linked_out_picking_id - out_backorder = out_picking.backorder_picking_id - - # Jika BU/OUT belum punya backorder, hubungkan BU/PICK backorder ke BU/OUT lama - if not out_backorder: - backorder.linked_out_picking_id = out_picking - else: - # Jika BU/OUT sudah punya backorder, hubungkan ke backorder BU/OUT - backorder.linked_out_picking_id = out_backorder - - elif 'BU/OUT/' in picking.name: - # Jika BU/OUT membuat backorder, update semua BU/PICK yang terhubung ke BU/OUT lama - pickings_to_update = self.env['stock.picking'].search([('linked_out_picking_id', '=', picking.id)]) - for pick in pickings_to_update: - pick.linked_out_picking_id = backorder - - return backorder - + total_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli") - @api.depends('total_so_koli') # Sesuaikan dengan field yang relevan + @api.depends('total_so_koli') def _compute_total_so_koli(self): for picking in self: - picking.total_so_koli = self.env['check.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id)]) + 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') # Sesuaikan dengan field yang relevan + @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)]) @@ -425,7 +396,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 }) @@ -820,7 +791,7 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): - if self.total_koli != self.total_so_koli: + 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.total_so_koli)) if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': @@ -870,8 +841,7 @@ class StockPicking(models.Model): self.validation_minus_onhand_quantity() self.responsible = self.env.user.id - if self.picking_type_code == 'internal' and 'BU/PICK/' in self.name: - self.send_koli_to_so() + # 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() @@ -890,21 +860,29 @@ class StockPicking(models.Model): def send_koli_to_so(self): for picking in self: - 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: # Hindari duplikasi - 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 == '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: + 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: @@ -981,62 +959,11 @@ class StockPicking(models.Model): res = super(StockPicking, self).action_cancel() return res - - def write(self, vals_list): - """ Override write method to auto-link BU/PICK to BU/OUT when necessary """ - records = super(StockPicking, self).write(vals_list) - for picking in records: - if 'BU/OUT/' in picking.name: - # Cari BU/PICK yang berhubungan berdasarkan logika tertentu - pick_picking = self.env['stock.picking'].search([ - ('name', 'like', 'BU/PICK/%'), - ('linked_out_picking_id', '=', False), - ('sale_id', '=', picking.sale_id.id) - ], limit=1) - - if pick_picking: - pick_picking.linked_out_picking_id = picking - - if 'BU/PICK/' in picking.name: - # Cari BU/PICK yang berhubungan berdasarkan logika tertentu - pick_picking = self.env['stock.picking'].search([ - ('name', 'like', 'BU/OUT/%'), - ('state', 'not in', ['cancel', 'done']), - ('sale_id', '=', picking.sale_id.id) - ], limit=1) - - if pick_picking: - pick_picking.linked_out_picking_id = picking - - return records - @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 records = super(StockPicking, self).create(vals) - for picking in records: - if 'BU/OUT/' in picking.name: - # Cari BU/PICK yang berhubungan berdasarkan logika tertentu - pick_picking = self.env['stock.picking'].search([ - ('name', 'like', 'BU/PICK'), - ('linked_out_picking_id', '=', False), - ('origin', '=', picking.origin) - ], limit=1) - - if pick_picking: - pick_picking.linked_out_picking_id = picking return records @@ -1367,6 +1294,7 @@ class CheckKoli(models.Model): copy=False, ) koli = fields.Char(string='Koli') + reserved_id = fields.Many2one('stock.picking', string='Reserved Picking') class ScanKoli(models.Model): _name = 'scan.koli' @@ -1388,6 +1316,35 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + def unlink(self): + for scan in self: + koli = scan.koli_id.koli_id + if koli: + # Hapus reserved_id saat scan dihapus + koli.reserved_id = False + + # Ambil semua scan koli yang masih ada dan memiliki picking_id yang sama + remaining_scans = self.env['scan.koli'].search([ + ('id', '!=', scan.id), # Kecuali scan yang sedang dihapus + ('koli_id.picking_id', '=', koli.picking_id.id) + ]) + + # Jika tidak ada scan lain yang memiliki picking_id yang sama, hapus linked_out_picking_id + if not remaining_scans: + koli.picking_id.linked_out_picking_id = False + + 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): """ Menghitung progres scan koli dalam format 'X/Y' """ for scan in self: @@ -1397,18 +1354,12 @@ class ScanKoli(models.Model): 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.model_create_multi - def create(self, vals_list): - """ Override create untuk update progress scan setelah scan koli ditambahkan """ - records = super(ScanKoli, self).create(vals_list) - for record in records: - record._compute_scan_koli_progress() - return records - @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): """ Validasi jika jumlah scan koli melebihi total SO koli """ for scan in self: + 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(scan.picking_id.scan_koli_lines) if total_scans > scan.picking_id.total_so_koli: raise UserError(_("Jumlah scan koli melebihi total SO koli!")) @@ -1418,9 +1369,20 @@ class ScanKoli(models.Model): if not self.koli_id: return - source_koli_so = self.picking_id.ids # Picking asal dari Koli yang dipilih - source_koli = self.koli_id.picking_id.linked_out_picking_id.ids - - # Cek apakah source_koli ditemukan + 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.onchange('koli_id') + def _onchange_koliii(self): + if self.koli_id and self.picking_id: + existing_koli = self.env['scan.koli'].search([ + ('picking_id', '=', self.picking_id.id), + ('koli_id', '=', self.koli_id.id), + ('id', '!=', self.id.origin) # Hindari validasi saat edit data + ]) + if existing_koli: + self.koli_id = False # Reset field koli_id agar pengguna tidak bisa memilihnya + raise UserError(f"Koli {existing_koli.koli_id.name} sudah dipindai dalam picking ini!") \ No newline at end of file diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 877208b0..4d31b072 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -401,6 +401,8 @@ + + diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 2a11459c..1b3406ec 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -73,10 +73,8 @@ - - @@ -219,7 +217,7 @@ scan.koli - + @@ -231,6 +229,7 @@ + -- cgit v1.2.3 From a9c4cd0c5ac694074f0e3a4359182a97f27f542e Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 24 Feb 2025 11:26:24 +0700 Subject: webhook biteship --- indoteknik_api/controllers/api_v1/stock_picking.py | 37 ++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index 3e58417f..01269724 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -138,7 +138,40 @@ class StockPicking(controller.Controller): 'name': picking_data.name }) - @http.route(prefix + 'n', auth='public', methods=['PUT', 'OPTIONS'], csrf=False) + @http.route(prefix + 'webhook/biteship', type='json', auth='public', methods=['POST'], csrf=False) def udpate_status_from_bitehsip(self, **kw): - picking_code = int(kw.get('picking_code', 0)) + try: + data = request.jsonrequest # Ambil data JSON dari request + event = data.get('event') + + # Log Webhook ke Model Odoo + request.env['webhook.logs'].sudo().create({ + 'event': event, + 'order_id': data.get('order_id'), + 'courier_tracking_id': data.get('courier_tracking_id'), + 'courier_waybill_id': data.get('courier_waybill_id'), + 'status': data.get('status'), + 'order_price': data.get('price'), + 'cash_on_delivery_fee': data.get('cash_on_delivery_fee'), + 'proof_of_delivery_fee': data.get('proof_of_delivery_fee'), + 'shippment_fee': data.get('shippment_fee'), + }) + + # Handle Event Berdasarkan Jenisnya + if event == "order.status": + self.process_order_status(data) + elif event == "order.price": + self.process_order_price(data) + elif event == "order.waybill_id": + self.process_order_waybill(data) + + return {'success': True, 'message': f'Webhook {event} received'} + except Exception as e: + return {'success': False, 'message': str(e)} + + def process_order_status(self, data): + """Update status order di Odoo""" + order = request.env['sale.order'].sudo().search([('id', '=', data.get('order_id'))], limit=1) + if order: + order.write({'state': data.get('status')}) \ No newline at end of file -- cgit v1.2.3 From 8696e202ecf594890a9ad29bc2bd2729321459c5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Feb 2025 14:58:11 +0700 Subject: add copy to field state_reserve and date_reserved --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 49e66786..954a5d52 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -94,7 +94,7 @@ class StockPicking(models.Model): purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', string="Purchase Representative") carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method') shipping_status = fields.Char(string='Shipping Status', compute="_compute_shipping_status") - date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya') + date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False) status_printed = fields.Selection([ ('not_printed', 'Belum Print'), ('printed', 'Printed') @@ -120,7 +120,7 @@ class StockPicking(models.Model): ('ready', 'Ready to Ship'), ('done', 'Done'), ('cancel', 'Cancelled'), - ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") + ], string='Status Reserve', readonly=True, tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note") @api.model -- cgit v1.2.3 From b20368190fbf08b87d8a665c4b316786e3d73141 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Feb 2025 15:36:22 +0700 Subject: state reserve --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 954a5d52..36d9f63d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -120,7 +120,7 @@ class StockPicking(models.Model): ('ready', 'Ready to Ship'), ('done', 'Done'), ('cancel', 'Cancelled'), - ], string='Status Reserve', readonly=True, tracking=True, copy=False, help="The current state of the stock picking.") + ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note") @api.model -- cgit v1.2.3 From 63c712cd38666723a112899d49af3ee82af9bf89 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 24 Feb 2025 16:04:59 +0700 Subject: adjusment number --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/stock_inventory.py | 59 ++++++++++++++++++++++++++ indoteknik_custom/security/ir.model.access.csv | 3 +- indoteknik_custom/views/stock_inventory.xml | 28 ++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 indoteknik_custom/models/stock_inventory.py create mode 100644 indoteknik_custom/views/stock_inventory.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index e74efb95..ac887547 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -161,6 +161,7 @@ 'report/report_picking.xml', 'report/report_sale_order.xml', 'views/coretax_faktur.xml', + 'views/stock_inventory.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index dea3eeea..f33a7411 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -143,3 +143,4 @@ from . import coretax_fatur from . import ir_actions_report from . import barcoding_product from . import account_payment_register +from . import stock_inventory diff --git a/indoteknik_custom/models/stock_inventory.py b/indoteknik_custom/models/stock_inventory.py new file mode 100644 index 00000000..12a891de --- /dev/null +++ b/indoteknik_custom/models/stock_inventory.py @@ -0,0 +1,59 @@ +from odoo import models, api, fields +from odoo.exceptions import UserError +from datetime import datetime +import logging + +_logger = logging.getLogger(__name__) + + +class StockInventory(models.Model): + _inherit = ['stock.inventory'] + _order = 'id desc' + _rec_name = 'number' + + number = fields.Char(string='Document No', index=True, copy=False, readonly=True) + adjusment_type = fields.Selection([ + ('in', 'Adjusment In'), + ('out', 'Adjusment Out'), + ], string='Adjusments Type', required=True) + + def _generate_number_stock_inventory(self): + """Men-generate nomor untuk semua stock inventory yang belum memiliki number.""" + stock_records = self.env['stock.inventory'].search([('number', '=', False)], order='id asc') + for record in stock_records: + self._assign_number(record) + + _logger.info('Generate Number Done') + + def _assign_number(self, record): + """Menentukan nomor berdasarkan kategori Adjust-In atau Adjust-Out.""" + name_upper = record.name.upper() if record.name else "" + + if self.adjusment_type == 'out' or "ADJUST OUT" in name_upper or "ADJUST-OUT" in name_upper or "OUT" in name_upper: + last_number = self._get_last_sequence("ADJUST/OUT/") + record.number = f"ADJUST/OUT/{last_number}" + elif self.adjusment_type == 'in' or "ADJUST IN" in name_upper or "ADJUST-IN" in name_upper or "IN" in name_upper: + last_number = self._get_last_sequence("ADJUST/IN/") + record.number = f"ADJUST/IN/{last_number}" + else: + record.number = "UNKNOWN" # Jika tidak termasuk kategori + + def _get_last_sequence(self, prefix): + """Mengambil nomor terakhir berdasarkan prefix (ADJUST/OUT/ atau ADJUST/IN/) dengan format 00001, 00002, dst.""" + last_record = self.env['stock.inventory'].search( + [("number", "like", f"{prefix}%")], order="number desc", limit=1 + ) + + if last_record and last_record.number: + try: + last_number = int(last_record.number.split("/")[-1]) # Ambil angka terakhir + return str(last_number + 1).zfill(5) # Format jadi 00001, 00002, dst. + except ValueError: + return "00001" # Jika format tidak valid, mulai dari 00001 + return "00001" # Jika belum ada data, mulai dari 00001 + + @api.model + def create(self, vals): + order = super(StockInventory, self).create(vals) + self._assign_number(order) + return order diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index de29468d..13ce5f98 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -162,4 +162,5 @@ access_confirm_approval_wizard,confirm.approval.wizard,model_confirm_approval_wi access_barcode_product,access.barcode.product,model_barcode_product,,1,1,1,1 access_barcoding_product,access.barcoding.product,model_barcoding_product,,1,1,1,1 access_barcoding_product_line,access.barcoding.product.line,model_barcoding_product_line,,1,1,1,1 -access_account_payment_register,access.account.payment.register,model_account_payment_register,,1,1,1,1 \ No newline at end of file +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 \ No newline at end of file diff --git a/indoteknik_custom/views/stock_inventory.xml b/indoteknik_custom/views/stock_inventory.xml new file mode 100644 index 00000000..db85f05c --- /dev/null +++ b/indoteknik_custom/views/stock_inventory.xml @@ -0,0 +1,28 @@ + + + + + stock.inventory.form.inherit + stock.inventory + + + + + + + + + + + + stock.inventory.tree.inherit + stock.inventory + + + + + + + + + -- cgit v1.2.3 From 1d2011c7b1b9766b0254479733b2ec226e8201bd Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 24 Feb 2025 17:16:45 +0700 Subject: add public holiday --- indoteknik_api/controllers/api_v1/product.py | 15 ++------ indoteknik_api/controllers/api_v1/sale_order.py | 4 ++- indoteknik_custom/models/sale_order.py | 48 ++++++++++++++++++++----- indoteknik_custom/models/stock_picking.py | 22 ++++++------ indoteknik_custom/views/sale_order.xml | 2 ++ 5 files changed, 57 insertions(+), 34 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py index 93ef305c..557215ea 100644 --- a/indoteknik_api/controllers/api_v1/product.py +++ b/indoteknik_api/controllers/api_v1/product.py @@ -7,18 +7,7 @@ import logging import math import json -_logger = logging.getLogger(__name__) - -def get_days_until_next_business_day(start_date=None, *args, **kwargs): - today = start_date or datetime.today().date() - offset = 0 # Counter jumlah hari yang ditambahkan - - while today.weekday() >= 5 : - today += timedelta(days=1) - offset += 1 - - return offset - +_logger = logging.getLogger(__name__) class Product(controller.Controller): @@ -88,7 +77,7 @@ class Product(controller.Controller): break start_date = datetime.today().date() - additional_days = get_days_until_next_business_day(start_date) + additional_days = request.env['sale.order'].get_days_until_next_business_day(start_date) # Jika semua loop selesai tanpa include_instant menjadi False return self.response({ diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index 8b95ade8..4afeb21b 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -386,7 +386,8 @@ class SaleOrder(controller.Controller): 'note_website': [], 'voucher': [], 'source': [], - 'estimated_arrival_days': ['number', 'default:0'] + 'estimated_arrival_days': ['number', 'default:0'], + 'estimated_arrival_days_start': ['number', 'default:0'] }) if not params['valid']: @@ -416,6 +417,7 @@ class SaleOrder(controller.Controller): 'partner_purchase_order_file': params['value']['po_file'], 'delivery_amt': params['value']['delivery_amount'] * 1.10, 'estimated_arrival_days': params['value']['estimated_arrival_days'], + 'estimated_arrival_days_start': params['value']['estimated_arrival_days_start'], 'shipping_cost_covered': 'customer', 'shipping_paid_by': 'customer', 'carrier_id': params['value']['carrier_id'], diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index d0b57a3d..d956e93a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -93,11 +93,13 @@ class SaleOrder(models.Model): applied_voucher_shipping_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False) amount_voucher_shipping_disc = fields.Float(string='Voucher Discount') source_id = fields.Many2one('utm.source', 'Source', domain="[('id', 'in', [32, 59, 60, 61])]", required=True) - estimated_arrival_days = fields.Integer('Estimated Arrival Days', default=0) + estimated_arrival_days = fields.Integer('Estimated Arrival To', default=0) + estimated_arrival_days_start = fields.Integer('Estimated Arrival From', default=0) email = fields.Char(string='Email') picking_iu_id = fields.Many2one('stock.picking', 'Picking IU') helper_by_id = fields.Many2one('res.users', 'Helper By') - eta_date = fields.Datetime(string='ETA Date', copy=False, compute='_compute_eta_date') + eta_date_start = fields.Datetime(string='ETA Date start', copy=False, compute='_compute_eta_start_date') + eta_date = fields.Datetime(string='ETA Date end', copy=False, compute='_compute_eta_date') flash_sale = fields.Boolean(string='Flash Sale', help='Data dari web') is_continue_transaction = fields.Boolean(string='Button Transaction', help='Data dari web') web_approval = fields.Selection([ @@ -146,9 +148,7 @@ class SaleOrder(models.Model): ]) estimated_ready_ship_date = fields.Datetime( string='ET Ready to Ship compute', - compute='_compute_etrts_date', - store=True - + compute='_compute_etrts_date' ) expected_ready_to_ship = fields.Datetime( string='ET Ready to Ship', @@ -156,7 +156,7 @@ class SaleOrder(models.Model): store=True ) shipping_method_picking = fields.Char(string='Shipping Method Picking', compute='_compute_shipping_method_picking') - + def _compute_shipping_method_picking(self): for order in self: if order.picking_ids: @@ -383,13 +383,43 @@ class SaleOrder(models.Model): rec.compute_fullfillment = True - @api.depends('date_order', 'estimated_arrival_days', 'state') + @api.depends('date_order', 'estimated_arrival_days', 'state', 'estimated_arrival_days_start') def _compute_eta_date(self): for rec in self: if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days: rec.eta_date = rec.date_order + timedelta(days=rec.estimated_arrival_days) + rec.eta_date_start = rec.date_order + timedelta(days=rec.estimated_arrival_days_start) else: rec.eta_date = False + rec.eta_date_start = False + + @api.depends('date_order', 'state', 'estimated_arrival_days_start') + def _compute_eta_start_date(self): + for rec in self: + if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days_start: + rec.eta_date_start = rec.date_order + timedelta(days=rec.estimated_arrival_days_start) + else: + rec.eta_date_start = False + + def get_days_until_next_business_day(self,start_date=None, *args, **kwargs): + today = start_date or datetime.today().date() + offset = 0 # Counter jumlah hari yang ditambahkan + holiday = self.env['hr.public.holiday'] + + while True : + today += timedelta(days=1) + offset += 1 + + if today.weekday() >= 5: + continue + + is_holiday = holiday.search([("start_date", "=", today)]) + if is_holiday: + continue + + break + + return offset @api.depends("order_line.product_id") def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date @@ -401,7 +431,7 @@ class SaleOrder(models.Model): max_slatime = max(max_slatime, slatime) if rec.date_order: - eta_date = datetime.now() + timedelta(days=max_slatime) + eta_date = rec.date_order + timedelta(days=self.get_days_until_next_business_day(rec.date_order)) + timedelta(days=max_slatime) rec.estimated_ready_ship_date = eta_date rec.commitment_date = eta_date # Jika expected_ready_to_ship kosong, set nilai default @@ -1470,7 +1500,7 @@ class SaleOrder(models.Model): def create(self, vals): # Ensure partner details are updated when a sale order is created order = super(SaleOrder, self).create(vals) - # order._compute_etrts_date() + order._compute_etrts_date() # order._update_partner_details() return order diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1690a4ed..be395cef 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -177,19 +177,19 @@ class StockPicking(models.Model): 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") - estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, store=True, related='sale_id.estimated_ready_ship_date') - countdown_hours = fields.Float(string='Countdown in Hours', compute='_compute_countdown_ready_to_ship', store=True, default=False) + estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, related='sale_id.estimated_ready_ship_date') + countdown_hours = fields.Float(string='Countdown in Hours', compute='_compute_countdown_hours', default=False) countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_compute_countdown_ready_to_ship') - # @api.depends('estimated_ready_ship_date', 'state') - # def _compute_countdown_hours(self): - # for record in self: - # if record.state in ('cancel', 'done') or not record.estimated_ready_ship_date: - # # Gunakan nilai yang sangat besar sebagai placeholder - # record.countdown_hours = 999999 - # else: - # delta = record.estimated_ready_ship_date - waktu.now() - # record.countdown_hours = delta.total_seconds() / 3600 + @api.depends('estimated_ready_ship_date', 'state') + def _compute_countdown_hours(self): + for record in self: + if record.state in ('cancel', 'done') or not record.estimated_ready_ship_date: + # Gunakan nilai yang sangat besar sebagai placeholder + record.countdown_hours = 999999 + else: + delta = record.estimated_ready_ship_date - waktu.now() + record.countdown_hours = delta.total_seconds() / 3600 @api.depends('estimated_ready_ship_date', 'state') def _compute_countdown_ready_to_ship(self): diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index b267eee4..4cc96cd2 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -68,6 +68,8 @@ + + -- cgit v1.2.3 From a5ab398f902915ea44394b4b8fd6a8bb14ccbb89 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 25 Feb 2025 09:32:14 +0700 Subject: change salef from nadia to boy --- indoteknik_api/controllers/api_v1/sale_order.py | 2 +- indoteknik_api/controllers/api_v1/user.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index 8b95ade8..5b7b59bd 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -426,7 +426,7 @@ class SaleOrder(controller.Controller): 'npwp': sales_partner.npwp or '0', # Get NPWP from partner 'sppkp': sales_partner.sppkp, # Get SPPKP from partner 'email': sales_partner.email, # Get Email from partner - 'user_id': 3222 # User ID: Nadia Rauhadatul Firdaus + 'user_id': 11314 # User ID: Boy Revandi } sales_partner = request.env['res.partner'].browse(parameters['partner_id']) diff --git a/indoteknik_api/controllers/api_v1/user.py b/indoteknik_api/controllers/api_v1/user.py index 1d26d356..c0974367 100644 --- a/indoteknik_api/controllers/api_v1/user.py +++ b/indoteknik_api/controllers/api_v1/user.py @@ -98,7 +98,7 @@ class User(controller.Controller): user.partner_id.npwp = '00.000.000.0-000.000' user.partner_id.sppkp = '-' user.partner_id.nama_wajib_pajak = user.name - user.partner_id.user_id = 3222 + user.partner_id.user_id = 11314 user.partner_id.property_account_receivable_id = 395 user.partner_id.property_account_payable_id = 438 data = { @@ -208,7 +208,7 @@ class User(controller.Controller): 'email': email_partner, 'street': alamat_bisnis, 'company_type': 'company', - 'user_id': 3222, + 'user_id': 11314, 'property_account_receivable_id': 395, 'property_account_payable_id': 438, 'active': False, @@ -253,7 +253,7 @@ class User(controller.Controller): user.partner_id.npwp = '00.000.000.0-000.000' user.partner_id.sppkp = '-' user.partner_id.nama_wajib_pajak = name - user.partner_id.user_id = 3222 + user.partner_id.user_id = 11314 user.partner_id.property_account_receivable_id= 395 user.partner_id.property_account_payable_id = 438 @@ -605,7 +605,7 @@ class User(controller.Controller): 'email': email_partner, 'street': alamat_bisnis, 'company_type': 'company', - 'user_id': 3222, + 'user_id': 11314, 'property_account_receivable_id': 395, 'property_account_payable_id': 438, 'active': False, -- cgit v1.2.3 From 0174a19b631d67a70805de45b252cdcf1c562fb6 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 10:28:15 +0700 Subject: push wms --- indoteknik_custom/models/stock_immediate_transfer.py | 6 ++++-- indoteknik_custom/models/stock_picking.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index ec00df7b..8724c567 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -9,14 +9,16 @@ class StockImmediateTransfer(models.TransientModel): 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: - line.picking_id.send_mail_bills() - line.picking_id.send_koli_to_so() 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 picking.state == 'draft': picking.action_confirm() if picking.state != 'assigned': diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f3af00d9..f359a2fb 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -848,6 +848,7 @@ class StockPicking(models.Model): self.calculate_line_no() self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' + self.send_koli_to_so() return res -- cgit v1.2.3 From 6352ba63a39293b3e260bd7bd933c9de2c023172 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 10:47:35 +0700 Subject: add purchase price po and so on po sales matches --- indoteknik_custom/models/purchase_order_sales_match.py | 7 +++++++ indoteknik_custom/views/purchase_order.xml | 2 ++ 2 files changed, 9 insertions(+) diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py index d1d929d3..d6c2a631 100644 --- a/indoteknik_custom/models/purchase_order_sales_match.py +++ b/indoteknik_custom/models/purchase_order_sales_match.py @@ -24,6 +24,13 @@ class PurchaseOrderSalesMatch(models.Model): margin_item = fields.Float(string='Margin') delivery_amt = fields.Float(string='Delivery Amount', compute='_compute_delivery_amt') margin_deduct = fields.Float(string='After Deduct', compute='_compute_delivery_amt') + purchase_price_so = fields.Float(string='Purchase Price Sale Order', related='sale_line_id.purchase_price') + purchase_price_po = fields.Float('Purchase Price PO', compute='_compute_purchase_price_po') + + def _compute_purchase_price_po(self): + for line in self: + purchase_price = self.env['purchase.order.line'].search([('order_id', '=', line.purchase_order_id.id), ('product_id', '=', line.product_id.id)]) + line.purchase_price_po = purchase_price.purchase_price def _compute_delivery_amt(self): for line in self: diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index 3e4dd89c..0d3d5cc2 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -296,6 +296,8 @@ + + -- cgit v1.2.3 From f7a149c71824ba40f9e585d1df287b36853b7213 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 10:49:49 +0700 Subject: fix bug --- indoteknik_custom/models/purchase_order_sales_match.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py index d6c2a631..4ebe959b 100644 --- a/indoteknik_custom/models/purchase_order_sales_match.py +++ b/indoteknik_custom/models/purchase_order_sales_match.py @@ -30,7 +30,7 @@ class PurchaseOrderSalesMatch(models.Model): def _compute_purchase_price_po(self): for line in self: purchase_price = self.env['purchase.order.line'].search([('order_id', '=', line.purchase_order_id.id), ('product_id', '=', line.product_id.id)]) - line.purchase_price_po = purchase_price.purchase_price + line.purchase_price_po = purchase_price.price_unit def _compute_delivery_amt(self): for line in self: -- cgit v1.2.3 From 787aa90ca730936c93e0afb250285ebc8708ad3a Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 25 Feb 2025 11:17:36 +0700 Subject: cr approved margin SO --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 430b4526..e23f39bc 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1122,7 +1122,7 @@ class SaleOrder(models.Model): return self.total_percent_margin < 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return self.total_percent_margin <= 20 and not self.env.user.is_leader and not self.env.user.is_sales_manager + return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 5fc94808e034dac8efeff3367b665dbd6b4f3df2 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 11:26:52 +0700 Subject: trying to fix bug margin po --- indoteknik_custom/models/purchase_order.py | 70 ++++++++++-------------------- 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 54d771ba..83f86e8e 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -943,63 +943,41 @@ class PurchaseOrder(models.Model): def compute_total_margin_from_apo(self): sum_so_margin = sum_sales_price = sum_margin = 0 - for line in self.order_sales_match_line: - po_line = self.env['purchase.order.line'].search([ - ('product_id', '=', line.product_id.id), - ('order_id', '=', line.purchase_order_id.id) - ], limit=1) - sale_order_line = line.sale_line_id + for line in self.order_line: + sale_order_line = line.so_line_id if not sale_order_line: sale_order_line = self.env['sale.order.line'].search([ ('product_id', '=', line.product_id.id), - ('order_id', '=', line.sale_id.id) + ('order_id', '=', line.so_id.id) ], limit=1, order='price_reduce_taxexcl') - if sale_order_line and po_line: - so_margin = (line.qty_po / line.qty_so) * sale_order_line.item_margin - sum_so_margin += so_margin - - sales_price = sale_order_line.price_reduce_taxexcl * line.qty_po - if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': - sales_price -= (sale_order_line.delivery_amt_line / sale_order_line.product_uom_qty) * line.qty_po - if sale_order_line.order_id.fee_third_party > 0: - sales_price -= (sale_order_line.fee_third_party_line / sale_order_line.product_uom_qty) * line.qty_po - sum_sales_price += sales_price - - - purchase_price = po_line.price_subtotal - if po_line.ending_price > 0: - if po_line.taxes_id.id == 22: - ending_price = po_line.ending_price / 1.11 - purchase_price = ending_price - else: - purchase_price = po_line.ending_price - if line.purchase_order_id.delivery_amount > 0: - purchase_price += (po_line.delivery_amt_line / po_line.product_qty) * line.qty_po - if line.purchase_order_id.delivery_amt > 0: - purchase_price += line.purchase_order_id.delivery_amt - real_item_margin = sales_price - purchase_price - sum_margin += real_item_margin - - if sum_so_margin != 0 and sum_sales_price != 0 and sum_margin != 0: - self.total_so_margin = sum_so_margin - self.total_so_percent_margin = round((sum_so_margin / sum_sales_price), 2) * 100 - self.total_margin = sum_margin - self.total_percent_margin = round((sum_margin / sum_sales_price), 2) * 100 - - else: - self.total_margin = 0 - self.total_percent_margin = 0 - self.total_so_margin = 0 - self.total_so_percent_margin = 0 - + sum_so_margin += sale_order_line.item_margin + sales_price = sale_order_line.price_reduce_taxexcl * sale_order_line.product_uom_qty + if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': + sales_price -= sale_order_line.delivery_amt_line + if sale_order_line.order_id.fee_third_party > 0: + sales_price -= sale_order_line.fee_third_party_line + sum_sales_price += sales_price + purchase_price = line.price_subtotal + if line.ending_price > 0: + if line.taxes_id.id == 22: + ending_price = line.ending_price / 1.11 + purchase_price = ending_price + else: + purchase_price = line.ending_price + # purchase_price = line.price_subtotal + if line.order_id.delivery_amount > 0: + purchase_price += line.delivery_amt_line + if line.order_id.delivery_amt > 0: + purchase_price += line.order_id.delivery_amt + real_item_margin = sales_price - purchase_price + sum_margin += real_item_margin if sum_so_margin != 0 and sum_sales_price != 0 and sum_margin != 0: self.total_so_margin = sum_so_margin self.total_so_percent_margin = round((sum_so_margin / sum_sales_price), 2) * 100 self.total_margin = sum_margin self.total_percent_margin = round((sum_margin / sum_sales_price), 2) * 100 - else: self.total_margin = 0 self.total_percent_margin = 0 -- cgit v1.2.3 From ab1ba4b2b482b207a39ae17e43cdac4b1abb72ce Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 11:38:21 +0700 Subject: trying to fix bug margin po --- indoteknik_custom/models/purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 83f86e8e..9c05780b 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -959,7 +959,7 @@ class PurchaseOrder(models.Model): sales_price -= sale_order_line.fee_third_party_line sum_sales_price += sales_price purchase_price = line.price_subtotal - if line.ending_price > 0: + if line.order_id.total_delivery_amount > 0 and line.order_id.total_cost_service > 0: if line.taxes_id.id == 22: ending_price = line.ending_price / 1.11 purchase_price = ending_price -- cgit v1.2.3 From 6eecc5ecd377e6e7b69519294259b8e66cd8e564 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 11:40:28 +0700 Subject: fix error --- indoteknik_custom/models/purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 9c05780b..faf0955d 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -959,7 +959,7 @@ class PurchaseOrder(models.Model): sales_price -= sale_order_line.fee_third_party_line sum_sales_price += sales_price purchase_price = line.price_subtotal - if line.order_id.total_delivery_amount > 0 and line.order_id.total_cost_service > 0: + if line.order_id.total_delivery_amt > 0 and line.order_id.total_cost_service > 0: if line.taxes_id.id == 22: ending_price = line.ending_price / 1.11 purchase_price = ending_price -- cgit v1.2.3 From 0f9e8f280cd50ed5af7cbc98253ebf02068d5f24 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 11:59:08 +0700 Subject: trying to fix bug margin po --- indoteknik_custom/models/purchase_order.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index faf0955d..6d02038d 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -944,20 +944,21 @@ class PurchaseOrder(models.Model): def compute_total_margin_from_apo(self): sum_so_margin = sum_sales_price = sum_margin = 0 for line in self.order_line: - sale_order_line = line.so_line_id - if not sale_order_line: - sale_order_line = self.env['sale.order.line'].search([ - ('product_id', '=', line.product_id.id), - ('order_id', '=', line.so_id.id) - ], limit=1, order='price_reduce_taxexcl') - - sum_so_margin += sale_order_line.item_margin - sales_price = sale_order_line.price_reduce_taxexcl * sale_order_line.product_uom_qty - if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': - sales_price -= sale_order_line.delivery_amt_line - if sale_order_line.order_id.fee_third_party > 0: - sales_price -= sale_order_line.fee_third_party_line - sum_sales_price += sales_price + sales_order_line = self.env['purchase.order.sales.match'].search([ + ('product_id', '=', line.product_id.id), + ('order_id', '=', line.so_id.id) + ]) + + for so_line in sales_order_line: + sale_order_line = so_line.sale_line_id + sum_so_margin += sale_order_line.item_margin + sales_price = sale_order_line.price_reduce_taxexcl * sale_order_line.product_uom_qty + if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': + sales_price -= sale_order_line.delivery_amt_line + if sale_order_line.order_id.fee_third_party > 0: + sales_price -= sale_order_line.fee_third_party_line + sum_sales_price += sales_price + purchase_price = line.price_subtotal if line.order_id.total_delivery_amt > 0 and line.order_id.total_cost_service > 0: if line.taxes_id.id == 22: -- cgit v1.2.3 From 780a7642c97518a3d7e6cdc5e2516b354f6e9cae Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 12:00:25 +0700 Subject: fix bug --- indoteknik_custom/models/purchase_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 6d02038d..71ee61d3 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -946,7 +946,7 @@ class PurchaseOrder(models.Model): for line in self.order_line: sales_order_line = self.env['purchase.order.sales.match'].search([ ('product_id', '=', line.product_id.id), - ('order_id', '=', line.so_id.id) + ('sale_id', '=', line.so_id.id) ]) for so_line in sales_order_line: @@ -958,7 +958,7 @@ class PurchaseOrder(models.Model): if sale_order_line.order_id.fee_third_party > 0: sales_price -= sale_order_line.fee_third_party_line sum_sales_price += sales_price - + purchase_price = line.price_subtotal if line.order_id.total_delivery_amt > 0 and line.order_id.total_cost_service > 0: if line.taxes_id.id == 22: -- cgit v1.2.3 From c36d351e79fb2b3487e391e8075bbdefaf41fbab Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 12:03:32 +0700 Subject: trying to fix bug margin po --- indoteknik_custom/models/purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 71ee61d3..f2401dec 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -946,7 +946,7 @@ class PurchaseOrder(models.Model): for line in self.order_line: sales_order_line = self.env['purchase.order.sales.match'].search([ ('product_id', '=', line.product_id.id), - ('sale_id', '=', line.so_id.id) + ('purchase_order_id', '=', line.order_id.id) ]) for so_line in sales_order_line: -- cgit v1.2.3 From 141bfb3a32e73d5b8557a70867d957d5ed3d485b Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 25 Feb 2025 13:31:31 +0700 Subject: update code --- indoteknik_custom/models/commision.py | 3 +++ indoteknik_custom/views/customer_commision.xml | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 03473b09..6920154a 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -233,6 +233,9 @@ class CustomerCommision(models.Model): return def action_confirm_customer_payment(self): + if self.status != 'approved': + raise UserError('Commision harus di approve terlebih dahulu sebelum di konfirmasi pembayarannya') + if self.payment_status == 'payment': raise UserError('Customer Commision sudah berstatus Payment') group_id = self.env.ref('indoteknik_custom.group_role_fat').id diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index 09031274..51172b1c 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -13,9 +13,9 @@ + decoration-success="payment_status == 'payment'" + decoration-danger="payment_status == 'pending'" + widget="badge"/> @@ -50,7 +50,7 @@ @@ -97,6 +109,8 @@ + + -- cgit v1.2.3 From 3f456ca27eaf98e5396da75f18e8106688491a46 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 11:53:55 +0700 Subject: update chek product bom --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4d186c8d..9d7be55a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -983,7 +983,7 @@ class SaleOrder(models.Model): def check_product_bom(self): for order in self: for line in order.order_line: - if 'bom' in line.product_id.default_code.lower() or 'bom-it' in line.name.lower(): + if 'bom-it' in line.name or 'bom' in line.product_id.default_code: 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') -- cgit v1.2.3 From 4b3c012f617683cdcb85251ac2da30d40ea4e093 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 13:04:17 +0700 Subject: fix code --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 9d7be55a..4d632c71 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -983,7 +983,7 @@ class SaleOrder(models.Model): def check_product_bom(self): for order in self: for line in order.order_line: - if 'bom-it' in line.name or 'bom' in line.product_id.default_code: + if 'bom-it' in line.product_id.name or 'bom' in line.product_id.default_code 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') -- cgit v1.2.3 From a06059e47feab3aa25c35652dfb83b8783273084 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 13:14:12 +0700 Subject: fix code check bom-it --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4d632c71..67434105 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -983,7 +983,7 @@ class SaleOrder(models.Model): def check_product_bom(self): for order in self: for line in order.order_line: - if 'bom-it' in line.product_id.name or 'bom' in line.product_id.default_code if line.product_id.default_code else False: + 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') -- cgit v1.2.3 From 2b34ec96718d693d7a322939b23bda2fe64d8c80 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 18 Mar 2025 15:25:34 +0700 Subject: push --- indoteknik_custom/models/commision.py | 24 +++++++++++++++++++----- indoteknik_custom/models/stock_picking.py | 2 ++ indoteknik_custom/views/customer_commision.xml | 2 ++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 62545984..d6b170d2 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -2,6 +2,7 @@ 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__) @@ -151,8 +152,7 @@ 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([ - ('draft', 'Menunggu Approval Manager Sales'), - ('pengajuan1', 'Menunggu Approval Sales'), + ('pengajuan1', 'Menunggu Approval Manager Sales'), ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), @@ -161,8 +161,7 @@ class CustomerCommision(models.Model): ('reject', 'Rejected'), ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange',default='draft') last_status = fields.Selection([ - ('draft', 'Menunggu Approval Manager Sales'), - ('pengajuan1', 'Menunggu Approval Sales'), + ('pengajuan1', 'Menunggu Approval Manager Sales'), ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), @@ -172,6 +171,7 @@ class CustomerCommision(models.Model): ], 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'), @@ -194,13 +194,27 @@ class CustomerCommision(models.Model): 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_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) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ab8109c7..fe8557c3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1011,6 +1011,8 @@ class StockPicking(models.Model): 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") + + res = super(StockPicking, self).action_cancel() return res diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index 7cdf3117..1d3f48fc 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -79,8 +79,10 @@ + +
-- cgit v1.2.3 From e7a1a6a1fbbc7e74291471d2abc9487511a8a861 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 15:39:13 +0700 Subject: fix code md gudang selisih --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1e93da80..9ea9f2a2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -133,7 +133,7 @@ class StockPicking(models.Model): notee = fields.Text(string="Note") state_approve_md = fields.Selection([ ('waiting', 'Waiting For Approve by MD'), - ('pending', 'Pending (cari dulu barangnya)'), + ('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") -- cgit v1.2.3 From 53501956695d6fcc881125cff69e8e5cc9ebf2f5 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 21:05:37 +0700 Subject: fix code --- indoteknik_custom/views/stock_picking.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index d6ec4d3b..ef56eec9 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -20,7 +20,7 @@ - + -- cgit v1.2.3 From e3ee409885f0b5ec1f1c229b2acec5f8b752c750 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 21:08:09 +0700 Subject: fix code --- indoteknik_custom/views/stock_picking.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index ef56eec9..ce07888a 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -71,17 +71,17 @@ type="object" attrs="{'invisible': [('carrier_id', '!=', 9)]}" /> - @@ -109,8 +109,8 @@ - - + + -- cgit v1.2.3 From 59dd3804fff70606c82db6a990973f396fe9fb0f Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 21:09:58 +0700 Subject: add xml --- indoteknik_custom/views/stock_picking.xml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index ce07888a..d6ec4d3b 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -20,7 +20,7 @@ - + @@ -71,17 +71,17 @@ type="object" attrs="{'invisible': [('carrier_id', '!=', 9)]}" /> - - - - - + @@ -109,8 +109,8 @@ - - + + -- cgit v1.2.3 From a12ed493887c4499f733f450a5ef826ee85b086e Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 21:13:57 +0700 Subject: back to code xml --- indoteknik_custom/views/stock_picking.xml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index d6ec4d3b..ce07888a 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -20,7 +20,7 @@ - + @@ -71,17 +71,17 @@ type="object" attrs="{'invisible': [('carrier_id', '!=', 9)]}" /> - @@ -109,8 +109,8 @@ - - + + -- cgit v1.2.3 From 61cec7a1d3595f661ba0fa1551e94f05ab0f27a2 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 22:10:20 +0700 Subject: fix code --- indoteknik_custom/views/stock_picking.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index ce07888a..687b2b82 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -20,7 +20,7 @@ - + @@ -71,17 +71,17 @@ type="object" attrs="{'invisible': [('carrier_id', '!=', 9)]}" /> - - - - - + -- cgit v1.2.3 From f275ebcd3813e9ea7af4b61f8bb48d030ad0ccc0 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 22:12:44 +0700 Subject: back to code --- indoteknik_custom/views/stock_picking.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 687b2b82..ce07888a 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -20,7 +20,7 @@ - + @@ -71,17 +71,17 @@ type="object" attrs="{'invisible': [('carrier_id', '!=', 9)]}" /> - -- cgit v1.2.3 From 4a7b5ebc82de37c6d2bde5e670066336256939d5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Mar 2025 09:55:02 +0700 Subject: cr reklas uang muka and permission button cancel stock picking --- indoteknik_custom/models/invoice_reklas.py | 6 ++++++ indoteknik_custom/models/stock_picking.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) 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/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ab8109c7..4229d33e 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1008,9 +1008,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 -- cgit v1.2.3 From fc5defa647bcdd317dc2d4069432c2dcc1141344 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 19 Mar 2025 10:04:31 +0700 Subject: change mthode validation expected date --- indoteknik_custom/models/sale_order.py | 87 ++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 7ccc551b..e2755eba 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -190,10 +190,10 @@ class SaleOrder(models.Model): ('PNR', 'Pareto Non Repeating'), ('NP', 'Non Pareto') ]) - estimated_ready_ship_date = fields.Datetime( - string='ET Ready to Ship compute', - compute='_compute_etrts_date' - ) + # estimated_ready_ship_date = fields.Datetime( + # string='ET Ready to Ship compute', + # compute='_compute_etrts_date' + # ) expected_ready_to_ship = fields.Datetime( string='ET Ready to Ship', copy=False, @@ -479,16 +479,6 @@ class SaleOrder(models.Model): break return offset - - # def calculate_sla_by_vendor(self, products): - # slatime = 15 - # for line in products: - # product_sla = self.env['product.sla'].search([('product_variant_id', '=', line.product_id.id)], limit=1) - # slatime = int(product_sla.sla) if product_sla and product_sla.sla and product_sla.sla != 'Indent' and "hari" in product_sla.sla.lower() else 15 - - # return { - # 'slatime' : slatime - # } def calculate_sla_by_vendor(self, products): product_ids = products.mapped('product_id.id') # Kumpulkan semua ID produk @@ -526,43 +516,55 @@ class SaleOrder(models.Model): return {'slatime': max_slatime, 'include_instant': include_instant} - @api.depends("order_line.product_id") + @api.depends("order_line.product_id", "date_order") def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date - if self.order_line: - for rec in self: - max_slatime = 1 # Default SLA jika tidak ada - slatime = self.calculate_sla_by_vendor(rec.order_line) - max_slatime = max(max_slatime, slatime['slatime']) - - current_date = datetime.now().date() + for rec in self: + if not rec.date_order: + rec.expected_ready_to_ship = False + return + + current_date = datetime.now().date() + + max_slatime = 1 # Default SLA jika tidak ada + slatime = self.calculate_sla_by_vendor(rec.order_line) + max_slatime = max(max_slatime, slatime['slatime']) - if rec.date_order: - sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 - if rec.source_id.name != 'Website': - rec.estimated_arrival_days = sum_days - - eta_date = current_date + timedelta(days=sum_days) - rec.estimated_ready_ship_date = eta_date - rec.commitment_date = eta_date - rec.expected_ready_to_ship = eta_date - - - - @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship - def _onchange_expected_ready_ship_date(self): + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 + if not rec.estimated_arrival_days: + rec.estimated_arrival_days = sum_days + + eta_date = current_date + timedelta(days=sum_days) + rec.commitment_date = eta_date + # Jika expected_ready_to_ship kosong, set nilai default + if not rec.expected_ready_to_ship: + rec.expected_ready_to_ship = eta_date + + def _validate_expected_ready_ship_date(self): for rec in self: - if rec.expected_ready_to_ship and rec.estimated_ready_ship_date: + if rec.expected_ready_to_ship and rec.commitment_date: + current_date = datetime.now().date() # Hanya membandingkan tanggal saja, tanpa jam expected_date = rec.expected_ready_to_ship.date() - estimated_date = rec.estimated_ready_ship_date.date() - if expected_date < estimated_date: - rec.expected_ready_to_ship = rec.estimated_ready_ship_date - rec.commitment_date = rec.estimated_ready_ship_date + max_slatime = 1 # Default SLA jika tidak ada + slatime = self.calculate_sla_by_vendor(rec.order_line) + max_slatime = max(max_slatime, slatime['slatime']) + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 + eta_minimum = current_date + timedelta(days=sum_days) + + if expected_date < eta_minimum: + rec.expected_ready_to_ship = eta_minimum raise ValidationError( "Tanggal 'Expected Ready to Ship' tidak boleh lebih kecil dari {}. Mohon pilih tanggal minimal {}." - .format(estimated_date.strftime('%d-%m-%Y'), estimated_date.strftime('%d-%m-%Y')) + .format(eta_minimum.strftime('%d-%m-%Y'), eta_minimum.strftime('%d-%m-%Y')) ) + else: + rec.commitment_date = rec.expected_ready_to_ship + + + @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship + def _onchange_expected_ready_ship_date(self): + self._validate_expected_ready_ship_date() def _set_etrts_date(self): for order in self: @@ -1623,6 +1625,7 @@ class SaleOrder(models.Model): # Ensure partner details are updated when a sale order is created order = super(SaleOrder, self).create(vals) order._compute_etrts_date() + order._validate_expected_ready_ship_date() # order._update_partner_details() return order -- cgit v1.2.3 From 4c8ff729c027654870b3cf71015e5c7ecec28a7b Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 19 Mar 2025 10:32:24 +0700 Subject: expected rts --- indoteknik_custom/models/sale_order.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index acad7729..b17df045 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -545,9 +545,8 @@ class SaleOrder(models.Model): max_slatime = max(max_slatime, slatime) return {'slatime': max_slatime, 'include_instant': include_instant} - - @api.depends("order_line.product_id", "date_order") - def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date + + def _calculate_etrts_date(self): for rec in self: if not rec.date_order: rec.expected_ready_to_ship = False @@ -565,9 +564,12 @@ class SaleOrder(models.Model): eta_date = current_date + timedelta(days=sum_days) rec.commitment_date = eta_date - # Jika expected_ready_to_ship kosong, set nilai default - if not rec.expected_ready_to_ship: - rec.expected_ready_to_ship = eta_date + rec.expected_ready_to_ship = eta_date + + @api.depends("order_line.product_id", "date_order") + def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date + self._calculate_etrts_date() + def _validate_expected_ready_ship_date(self): for rec in self: @@ -1713,5 +1715,5 @@ class SaleOrder(models.Model): res = super(SaleOrder, self).write(vals) if any(field in vals for field in ["order_line", "client_order_ref"]): - self._compute_etrts_date() + self._calculate_etrts_date() return res \ No newline at end of file -- cgit v1.2.3 From 8a23309d00183fb66f1e8c57f0087439fdb999ec Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Wed, 19 Mar 2025 10:33:29 +0700 Subject: update renca banner --- indoteknik_api/controllers/api_v1/banner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_api/controllers/api_v1/banner.py b/indoteknik_api/controllers/api_v1/banner.py index aea8f0d9..64a6167b 100644 --- a/indoteknik_api/controllers/api_v1/banner.py +++ b/indoteknik_api/controllers/api_v1/banner.py @@ -45,7 +45,7 @@ class Banner(controller.Controller): if not keyword: banners = request.env['x_banner.banner'].search(query, limit=limit, offset=offset, order=order) else: - banners = banner_kumpulan if banner_kumpulan else request.env['x_banner.banner'].search(query, limit=limit, offset=offset, order=order) + banners = banner_kumpulan if len(banner_kumpulan) > 0 else request.env['x_banner.banner'].search(query, limit=limit, offset=offset, order=order) week_number = self.get_week_number_of_current_month() -- cgit v1.2.3 From 1f324148f176bafc471a5948b8c5322a9b175ffa Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 19 Mar 2025 12:49:44 +0700 Subject: request iman --- indoteknik_custom/models/stock_picking.py | 8 ++++---- indoteknik_custom/views/stock_picking.xml | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 9e5fca66..23ddb47f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -136,11 +136,11 @@ class StockPicking(models.Model): ('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") + # 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" + # 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): diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index ce07888a..72fdefa7 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -20,7 +20,7 @@ - + @@ -71,17 +71,17 @@ type="object" attrs="{'invisible': [('carrier_id', '!=', 9)]}" /> - - - - - + @@ -110,7 +110,7 @@ - + -- cgit v1.2.3 From 99cf4f8d2a109f09049a52bccdb85dde6aec1081 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Mar 2025 13:17:52 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 23ddb47f..b8a83d5c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1056,7 +1056,7 @@ class StockPicking(models.Model): return True def action_cancel(self): - if not self.env.user.is_logistic_approver: + if not self.env.user.is_logistic_approver and (self.env.context.get('active_model') == 'stock.picking' or self.env.context.get('active_model') == 'stock.picking.type'): if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") -- cgit v1.2.3 From cc1759574f76b084a1ce44e1acf01ed20dcdd729 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Mar 2025 13:34:25 +0700 Subject: fix bug button cancel stock picking --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b8a83d5c..6c6cbaa1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1060,7 +1060,7 @@ class StockPicking(models.Model): 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'): + if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing': raise UserError("Button ini hanya untuk Logistik") res = super(StockPicking, self).action_cancel() -- cgit v1.2.3 From c48f3204c84cf7fca8da827178c4971370b324f8 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 20 Mar 2025 10:16:37 +0700 Subject: update logic duplicatee contact --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index abcb6f2f..caa420e5 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -548,20 +548,7 @@ class UserPengajuanTempoRequest(models.Model): ('name', '=', contact_data['name']) ], limit=1) - if existing_contact: - # Pastikan tidak ada duplikasi nama dalam perusahaan yang sama - duplicate_check = self.env['res.partner'].search([ - ('name', '=', contact_data['name']), - ('id', '!=', existing_contact.id) # Hindari update yang menyebabkan duplikasi global - ], limit=1) - - if not duplicate_check: - # Perbarui hanya field yang tidak menyebabkan konflik - update_data = {k: v for k, v in contact_data.items() if k != 'name'} - existing_contact.write(update_data) - else: - raise UserError(f"Skipping update for {contact_data['name']} due to existing duplicate.") - else: + if not existing_contact: # Pastikan tidak ada partner lain dengan nama yang sama sebelum membuat baru duplicate_check = self.env['res.partner'].search([ ('name', '=', contact_data['name']) -- cgit v1.2.3 From 3ed91948307260a25efae332c6dae013d276fef5 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 20 Mar 2025 15:05:01 +0700 Subject: add sale order post massage when confirm product bom --- indoteknik_custom/models/mrp_production.py | 17 +++++++++++++++-- indoteknik_custom/views/mrp_production.xml | 2 ++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 54d90256..0bf98702 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -6,5 +6,18 @@ class MrpProduction(models.Model): _inherit = 'mrp.production' desc = fields.Text(string='Description') - - \ No newline at end of file + sale_order = fields.Many2one('sale.order', string='Sale Order', required=True, copy=False) + + def action_confirm(self): + """Override action_confirm untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'.""" + if self._name != 'mrp.production': + return super(MrpProduction, self).action_confirm() + + result = super(MrpProduction, self).action_confirm() + + for record in self: + if record.sale_order and record.state == 'confirmed': + 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 diff --git a/indoteknik_custom/views/mrp_production.xml b/indoteknik_custom/views/mrp_production.xml index f81d65e8..95f419f6 100644 --- a/indoteknik_custom/views/mrp_production.xml +++ b/indoteknik_custom/views/mrp_production.xml @@ -7,6 +7,7 @@ + @@ -18,6 +19,7 @@ + -- cgit v1.2.3 From beb653de0340d270f2d56dd7b7145c3552e91ab4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Mar 2025 09:37:33 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 42 +++++++++++++++++++++++++- indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/stock_picking.xml | 15 ++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index c5b6387d..c3febc02 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -23,9 +23,10 @@ _biteship_api_key = "biteship_live.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) - _order = 'final_seq ASC' 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) @@ -1585,6 +1586,29 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + @api.onchange('koli_id') + def _onchange_koli_compare_with_konfirm_koli(self): + if not self.koli_id: + return + + # Pastikan konfirm_koli_lines tidak kosong + if not self.picking_id.konfirm_koli_lines: + raise UserError(_('Konfirm Koli Harus Diisi!')) + + # Ambil origin picking dari koli yang dipilih + koli_picking = self.koli_id.picking_id._origin + + # Kumpulkan semua origin picking dari konfirm koli lines + konfirm_pick_ids = [ + line.pick_id._origin + for line in self.picking_id.konfirm_koli_lines + if line.pick_id + ] + + # Validasi apakah koli_picking ada dalam daftar konfirmasi + if koli_picking not in konfirm_pick_ids: + raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + @api.constrains('picking_id', 'koli_id') def _check_duplicate_koli(self): for record in self: @@ -1713,6 +1737,22 @@ class ScanKoli(models.Model): # 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') + class WarningModalWizard(models.TransientModel): _name = 'warning.modal.wizard' _description = 'Peringatan Koli Belum Diperiksa' diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 57e02363..d3905d41 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -155,6 +155,7 @@ 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 diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 144ed820..67593b5b 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -208,7 +208,10 @@ - + + + + @@ -227,6 +230,16 @@ + + konfirm.koli.tree + konfirm.koli + + + + + + + check.koli.tree check.koli -- cgit v1.2.3 From 5825faf58e86681f62eeeaf783bb2ac0c01afbf7 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 24 Mar 2025 09:52:35 +0700 Subject: cr name request by widya --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index caa420e5..565b0315 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -110,7 +110,7 @@ class UserPengajuanTempoRequest(models.Model): pic_tittle = fields.Char(string='Tittle PIC Penerimaan Barang', related='pengajuan_tempo_id.pic_tittle', store=True, readonly=False) pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Barang', related='pengajuan_tempo_id.pic_mobile', store=True, readonly=False) pic_name = fields.Char(string='Nama PIC Penerimaan Barang', related='pengajuan_tempo_id.pic_name', store=True, readonly=False) - street_pengiriman = fields.Char(string="Alamat Perusahaan", related='pengajuan_tempo_id.street_pengiriman', store=True, readonly=False) + street_pengiriman = fields.Char(string="Alamat Pengiriman Barang", related='pengajuan_tempo_id.street_pengiriman', store=True, readonly=False) state_id_pengiriman = fields.Many2one('res.country.state', string='State', related='pengajuan_tempo_id.state_id_pengiriman', store=True, readonly=False) city_id_pengiriman = fields.Many2one('vit.kota', string='City', related='pengajuan_tempo_id.city_id_pengiriman', store=True, readonly=False) district_id_pengiriman = fields.Many2one('vit.kecamatan', string='Kecamatan',related='pengajuan_tempo_id.district_id_pengiriman', store=True, readonly=False) @@ -119,7 +119,7 @@ class UserPengajuanTempoRequest(models.Model): invoice_pic_tittle = fields.Char(string='Tittle PIC Penerimaan Invoice', related='pengajuan_tempo_id.invoice_pic_tittle', store=True, readonly=False) invoice_pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Invoice', related='pengajuan_tempo_id.invoice_pic_mobile', store=True, readonly=False) invoice_pic = fields.Char(string='Nama PIC Penerimaan Invoice', related='pengajuan_tempo_id.invoice_pic', store=True, readonly=False) - street_invoice = fields.Char(string="Alamat Perusahaan", related='pengajuan_tempo_id.street_invoice', store=True, readonly=False) + street_invoice = fields.Char(string="Alamat Pengiriman Invoice", related='pengajuan_tempo_id.street_invoice', store=True, readonly=False) state_id_invoice = fields.Many2one('res.country.state', string='State', related='pengajuan_tempo_id.state_id_invoice', store=True, readonly=False) city_id_invoice = fields.Many2one('vit.kota', string='City', related='pengajuan_tempo_id.city_id_invoice', store=True, readonly=False) district_id_invoice = fields.Many2one('vit.kecamatan', string='Kecamatan', related='pengajuan_tempo_id.district_id_invoice', store=True, readonly=False) -- cgit v1.2.3 From 99626f917b032110fe12b9a0ee86c218c0367be1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Mar 2025 15:34:56 +0700 Subject: push --- indoteknik_custom/models/mrp_production.py | 169 ++++++++++++++++++++++++- indoteknik_custom/models/stock_move.py | 14 ++ indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/mrp_production.xml | 30 +++++ 4 files changed, 212 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 54d90256..ed05de91 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 @@ -6,5 +9,167 @@ class MrpProduction(models.Model): _inherit = 'mrp.production' desc = fields.Text(string='Description') + status = fields.Selection([('pending', 'Pending'), ('approved', 'Approved'), ('reject', 'Reject')], string='Status', default='pending', tracking=3) + production_purchase_match = fields.One2many('production.purchase.match', 'production_id', string='Purchase Matches', auto_join=True) + + def action_approve(self): + if self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and self.status == 'pending': + self.status = 'approved' + + def action_reject(self): + if self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and self.status == 'pending': + self.status = 'reject' + + def create_po_from_manufacturing(self): + if not self.status == 'approved': + raise UserError('Harus Di Approve oleh Merchandiser') + + 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, + # '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, + 'taxes_id': [taxes], + } + 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 + if override_vendor: + query = [('product_id', '=', product_id.id), + ('vendor_id', '=', override_vendor.id)] + purchase_price = self.env['purchase.pricelist'].search(query, limit=1) + 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 - \ No newline at end of file + +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/stock_move.py b/indoteknik_custom/models/stock_move.py index 6b631713..b5fc782e 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -13,6 +13,20 @@ 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.onchange('product_id') + def onchange_product_to_fill_vendor(self): + if self.product_id: + if self.product_id.x_manufacture.override_vendor_id: + self.vendor_id = self.product_id.x_manufacture.override_vendor_id.id + else: + purchase_pricelist = self.env['purchase.pricelist'].search( + [('product_id', '=', product_id.id), + ('is_winner', '=', True)], + limit=1) + if purchase_pricelist: + self.vendor_id = purchase_pricelist.vendor_id.id def _compute_qr_code_variant(self): for rec in self: diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 4d0e51eb..8e9a08f0 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -168,3 +168,4 @@ access_account_payment_register,access.account.payment.register,model_account_pa 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 +access_production_purchase_match,access.production.purchase.match,model_production_purchase_match,,1,1,1,1 diff --git a/indoteknik_custom/views/mrp_production.xml b/indoteknik_custom/views/mrp_production.xml index f81d65e8..a28cdff8 100644 --- a/indoteknik_custom/views/mrp_production.xml +++ b/indoteknik_custom/views/mrp_production.xml @@ -5,9 +5,27 @@ mrp.production + + + + + + + + + + + + @@ -21,4 +39,16 @@ + + + production.purchase.match.tree + production.purchase.match + + + + + + + + -- cgit v1.2.3 From d094b9634a6d32549655c99ff370e45fb568f11d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Mar 2025 11:06:32 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index c3febc02..0e425f68 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -937,9 +937,12 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): + if len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + 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': 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")) @@ -1593,7 +1596,7 @@ class ScanKoli(models.Model): # Pastikan konfirm_koli_lines tidak kosong if not self.picking_id.konfirm_koli_lines: - raise UserError(_('Konfirm Koli Harus Diisi!')) + raise UserError(_('Mapping Koli Harus Diisi!')) # Ambil origin picking dari koli yang dipilih koli_picking = self.koli_id.picking_id._origin @@ -1607,7 +1610,7 @@ class ScanKoli(models.Model): # Validasi apakah koli_picking ada dalam daftar konfirmasi if koli_picking not in konfirm_pick_ids: - raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) @api.constrains('picking_id', 'koli_id') def _check_duplicate_koli(self): -- cgit v1.2.3 From db3ff0677f9c6ddd0f04ebcf3e9c780045259f73 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Mar 2025 13:48:16 +0700 Subject: fix get purchase pricelist --- indoteknik_custom/models/mrp_production.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index ed05de91..0e17fda9 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -98,8 +98,8 @@ class MrpProduction(models.Model): '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, - 'taxes_id': [taxes], + '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]) @@ -113,10 +113,10 @@ class MrpProduction(models.Model): def _get_purchase_price(self, product_id): override_vendor = product_id.x_manufacture.override_vendor_id - if override_vendor: - query = [('product_id', '=', product_id.id), - ('vendor_id', '=', override_vendor.id)] - purchase_price = self.env['purchase.pricelist'].search(query, limit=1) + 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( -- cgit v1.2.3 From 459790b31fac1a789efcb946f26938b60b123aab Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Mar 2025 15:15:05 +0700 Subject: add field date_approve and position --- indoteknik_custom/models/commision.py | 27 ++++++++++++++++++++------ indoteknik_custom/views/customer_commision.xml | 3 +-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index d6b170d2..f94d3c7a 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -156,7 +156,6 @@ class CustomerCommision(models.Model): ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), - ('pengajuan5', 'Menunggu Approval Finance'), ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange',default='draft') @@ -165,7 +164,6 @@ class CustomerCommision(models.Model): ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), - ('pengajuan5', 'Menunggu Approval Finance'), ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status') @@ -194,6 +192,18 @@ class CustomerCommision(models.Model): 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() @@ -283,25 +293,31 @@ class CustomerCommision(models.Model): return result 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.is_sales_manager: self.status = 'pengajuan2' 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: - self.status = 'pengajuan5' - self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name - elif self.status == 'pengajuan5' and self.env.user.id == 23: 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 @@ -320,7 +336,6 @@ class CustomerCommision(models.Model): 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') diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index 1d3f48fc..a5f0e07f 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -55,8 +55,7 @@ - - - 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 @@ + 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 @@ + -- cgit v1.2.3 From b105f669873645f29314be55aac45d95d0556970 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 27 Mar 2025 16:20:05 +0700 Subject: fix bug --- indoteknik_custom/models/mrp_production.py | 7 ++++++- indoteknik_custom/models/stock_move.py | 21 +++++++++++---------- indoteknik_custom/views/mrp_production.xml | 3 ++- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 1813dbeb..d80df2ce 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -11,6 +11,7 @@ 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'.""" @@ -31,6 +32,9 @@ class MrpProduction(models.Model): 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: @@ -115,7 +119,8 @@ class MrpProduction(models.Model): 'production_id': self.id, 'order_id': new_po.id }]) - # self.is_po = True + + self.is_po = True return po_ids diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 87b1c94e..514acad0 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -17,16 +17,17 @@ class StockMove(models.Model): @api.constrains('product_id') def constrains_product_to_fill_vendor(self): - if self.product_id: - if self.product_id.x_manufacture.override_vendor_id: - self.vendor_id = self.product_id.x_manufacture.override_vendor_id.id - else: - purchase_pricelist = self.env['purchase.pricelist'].search( - [('product_id', '=', product_id.id), - ('is_winner', '=', True)], - limit=1) - if purchase_pricelist: - self.vendor_id = purchase_pricelist.vendor_id.id + 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/views/mrp_production.xml b/indoteknik_custom/views/mrp_production.xml index ffbc8659..f8278f39 100644 --- a/indoteknik_custom/views/mrp_production.xml +++ b/indoteknik_custom/views/mrp_production.xml @@ -6,11 +6,12 @@ + -- cgit v1.2.3 From b9a81c1a9b495571a5cb30993a31eda7c5ab871f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 7 Apr 2025 10:42:33 +0700 Subject: cr validation delivery amt --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 6dd31d89..8d9af692 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -668,7 +668,7 @@ 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') + # @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') and not self.env.context.get('active_id', []): -- cgit v1.2.3 From ccd98307c3b48b25bbbb053caa2dba0cce5117d1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 9 Apr 2025 17:07:30 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 36 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0e425f68..be033b39 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -943,8 +943,8 @@ class StockPicking(models.Model): if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': 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 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.")) @@ -1594,21 +1594,17 @@ class ScanKoli(models.Model): if not self.koli_id: return - # Pastikan konfirm_koli_lines tidak kosong if not self.picking_id.konfirm_koli_lines: raise UserError(_('Mapping Koli Harus Diisi!')) - # Ambil origin picking dari koli yang dipilih koli_picking = self.koli_id.picking_id._origin - # Kumpulkan semua origin picking dari konfirm koli lines konfirm_pick_ids = [ line.pick_id._origin for line in self.picking_id.konfirm_koli_lines if line.pick_id ] - # Validasi apakah koli_picking ada dalam daftar konfirmasi if koli_picking not in konfirm_pick_ids: raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) @@ -1619,20 +1615,18 @@ class ScanKoli(models.Model): existing_koli = self.search([ ('picking_id', '=', record.picking_id.id), ('koli_id', '=', record.koli_id.id), - ('id', '!=', record.id) # Exclude current record + ('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')) # Ambil semua picking_id yang terpengaruh + picking_ids = set(self.mapped('koli_id.picking_id.id')) for scan in self: koli = scan.koli_id.koli_id if koli: - # Hapus reserved_id saat scan dihapus koli.reserved_id = False - # Periksa ulang apakah masih ada scan.koli yang tersisa untuk setiap picking_id for picking_id in picking_ids: remaining_scans = self.env['sales.order.koli'].search_count([ ('koli_id.picking_id', '=', picking_id) @@ -1640,8 +1634,6 @@ class ScanKoli(models.Model): delete_koli = len(self.filtered(lambda rec: rec.koli_id.picking_id.id == picking_id)) - - # Jika tidak ada scan.koli lain yang tersisa, set linked_out_picking_id ke False if remaining_scans == delete_koli: picking = self.env['stock.picking'].browse(picking_id) picking.linked_out_picking_id = False @@ -1666,7 +1658,6 @@ class ScanKoli(models.Model): scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin def _compute_scan_koli_progress(self): - """ Menghitung progres scan koli dalam format 'X/Y' """ for scan in self: if scan.picking_id: all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') @@ -1676,7 +1667,6 @@ class ScanKoli(models.Model): @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): - """ Validasi jika jumlah scan koli melebihi total SO koli """ 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 @@ -1709,24 +1699,21 @@ class ScanKoli(models.Model): koli_count_by_picking = defaultdict(int) for scan in self: - koli_count_by_picking[scan.koli_id.picking_id.id] += 1 # Hitung jumlah koli per picking + 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: - # Ambil stock moves dari BU/PICK dan BU/OUT berdasarkan picking_id 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)]) - # Sesuaikan product_id di BU/OUT dengan BU/PICK 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): - """Set qty_done ke 0 hanya jika tidak ada scan.koli tersisa untuk picking_id tersebut.""" product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) for move in product_bu_pick: @@ -1756,6 +1743,18 @@ class KonfirmKoli(models.Model): ) 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' @@ -1765,7 +1764,6 @@ class WarningModalWizard(models.TransientModel): picking_id = fields.Many2one('stock.picking') def action_continue(self): - """Lanjutkan validasi setelah menutup wizard""" if self.picking_id: return self.picking_id.with_context(skip_koli_check=True).button_validate() return {'type': 'ir.actions.act_window_close'} -- cgit v1.2.3 From e1edb44855ed2549f6f5a35773c5fdb81de9c0b4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 09:07:24 +0700 Subject: comment function check_product_bom on action confirm so --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8d9af692..8d156943 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1257,7 +1257,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: - order.check_product_bom() + # order.check_product_bom() order.check_credit_limit() order.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: -- cgit v1.2.3 From 4cfda3f1511ba1e6f8226652cf1ff64a48efef92 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 09:18:51 +0700 Subject: uncomment function check_product_bom on so --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8d156943..8d9af692 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1257,7 +1257,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: - # order.check_product_bom() + order.check_product_bom() order.check_credit_limit() order.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: -- cgit v1.2.3 From 00c69ce93bdb0071cd563be855857d2137115868 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 13:36:30 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 35 ++++++++++++++++++++++++++++--- indoteknik_custom/views/stock_picking.xml | 3 ++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index be033b39..3aa18233 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -195,6 +195,12 @@ class StockPicking(models.Model): # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) final_seq = fields.Float(string='Remaining Time') + @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): @@ -1568,6 +1574,19 @@ class CheckKoli(models.Model): ) 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' @@ -1589,6 +1608,15 @@ class ScanKoli(models.Model): 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: @@ -1661,9 +1689,10 @@ class ScanKoli(models.Model): for scan in self: if scan.picking_id: all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') - 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" + 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): diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 67593b5b..d6850b2f 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -244,9 +244,10 @@ check.koli.tree check.koli - + + -- cgit v1.2.3 From b3ae9b237bb4ec2861ab6e1e6fc2fad85358fe77 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 13:37:20 +0700 Subject: add brand ryu spareparts on purchasing job --- indoteknik_custom/models/purchasing_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 862e72c7..ea2f46cb 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -67,7 +67,7 @@ class PurchasingJob(models.Model): 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 -- cgit v1.2.3 From 10e3915bccc758ffc2f6e0e1d2e19f590605339e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 14:37:06 +0700 Subject: add optional hide on commision tree --- indoteknik_custom/views/customer_commision.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index a78f8a9f..9f0e1e8a 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -17,8 +17,8 @@ decoration-danger="payment_status == 'pending'" widget="badge"/> - - + + -- cgit v1.2.3 From 4147989e776d82a0a8b06a0ff8901e2146b0bd57 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 13:52:14 +0700 Subject: comment validasi validate bu out --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b1243e95..18edd497 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -995,11 +995,11 @@ 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' - if len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': - raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) + # if len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + # 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': - raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + # if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + # 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")) -- cgit v1.2.3 From a24177e4f4f575ea95ebc1d886b830da5c320690 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 14:01:52 +0700 Subject: fix case old so and new so wms validation --- indoteknik_custom/models/stock_picking.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 18edd497..1987c03c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -995,11 +995,19 @@ 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' - # if len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': - # 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': - # raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + threshold_datetime = datetime(2025, 4, 11, 13, 26) + + if (len(self.konfirm_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and (self.create_date or datetime.now()) > threshold_datetime): + 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 or datetime.now()) > threshold_datetime): + 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")) -- cgit v1.2.3 From 52b493aaee7c1782c328d2f3af7bee6534342734 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 14:06:29 +0700 Subject: fix error --- indoteknik_custom/models/stock_picking.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1987c03c..932e394b 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -995,18 +995,18 @@ 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 = datetime(2025, 4, 11, 13, 26) + threshold_datetime = waktu(2025, 4, 11, 13, 26) if (len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and (self.create_date or datetime.now()) > threshold_datetime): + and (self.create_date or waktu.utcnow()) > threshold_datetime): 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 or datetime.now()) > threshold_datetime): + and (self.create_date or waktu.utcnow()) > threshold_datetime): 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': -- cgit v1.2.3 From 9ca20d51a0aad50ea3df9bd878735c2fb8aadcc3 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 15:32:39 +0700 Subject: push wms --- indoteknik_custom/models/stock_picking.py | 23 +++++++++++++++++------ indoteknik_custom/views/stock_picking.xml | 3 ++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 932e394b..19b7517c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -148,8 +148,16 @@ class StockPicking(models.Model): # 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') - + @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): @@ -995,18 +1003,18 @@ 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, 13, 26) + 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 or waktu.utcnow()) > threshold_datetime): + and self.create_date > threshold_datetime): 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 or waktu.utcnow()) > threshold_datetime): + and self.create_date > threshold_datetime): 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': @@ -1015,9 +1023,12 @@ class StockPicking(models.Model): 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.total_so_koli)) + % (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: @@ -1847,7 +1858,7 @@ class KonfirmKoli(models.Model): 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' diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 7b4ba2f8..7d1153e0 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -20,7 +20,7 @@ - + @@ -89,6 +89,7 @@ + -- cgit v1.2.3 From 1def3707b2392fa17fb71cc70051bbe76cda47aa Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 17:03:27 +0700 Subject: change request state reserve stock picking --- indoteknik_custom/models/stock_picking.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 19b7517c..fd9daec9 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -649,13 +649,13 @@ class StockPicking(models.Model): def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'outgoing'), - ('name', 'ilike', 'BU/OUT/'), + ('picking_type_code', '=', 'internal'), + ('name', 'ilike', 'BU/PICK/'), ]) count = self.search_count([ ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'outgoing') + ('picking_type_code', '=', 'internal') ]) for picking in pickings: @@ -675,8 +675,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'), + ('name', 'ilike', 'BU/PICK/'), + ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) -- cgit v1.2.3 From 487772c771e72a77ae4d9e9c865d94d015f1ea5b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 10:44:12 +0700 Subject: refactor code state_reserve --- indoteknik_custom/models/stock_picking.py | 29 +---------------------------- indoteknik_custom/views/stock_picking.xml | 1 + 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index fd9daec9..558e13e6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -215,6 +215,7 @@ class StockPicking(models.Model): # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) final_seq = fields.Float(string='Remaining Time') + shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): @@ -626,37 +627,15 @@ 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', '=', 'internal'), ('name', 'ilike', 'BU/PICK/'), ]) - - count = self.search_count([ - ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'internal') - ]) for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ @@ -679,12 +658,6 @@ class StockPicking(models.Model): ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) - - count = self.search_count([ - ('backorder_id', '!=', False), - ('picking_type_code', '=', 'outgoing'), - ('state', 'not in', ['cancel', 'draft', 'done']) - ]) for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 7d1153e0..5424f3d3 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -84,6 +84,7 @@ /> + -- cgit v1.2.3 From 17b8688b83b65b0c21034ffcc9e51baf1099618b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 11:19:41 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 558e13e6..2aca5003 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -978,17 +978,17 @@ class StockPicking(models.Model): 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): - 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): - raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + # 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): + # 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): + # 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")) -- cgit v1.2.3 From 14fb9b00d18a7a0a3746106a1303f4ff1c13c356 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 11:21:06 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 2aca5003..558e13e6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -978,17 +978,17 @@ class StockPicking(models.Model): 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): - # 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): - # raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + 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): + 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): + 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")) -- cgit v1.2.3 From b73d16bf8dc0546190c9853f3e32a9aeaae3c1f0 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 13:17:02 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 1 + 1 file changed, 1 insertion(+) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 558e13e6..4e926e60 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1056,6 +1056,7 @@ class StockPicking(models.Model): 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.send_koli_to_so() -- cgit v1.2.3 From a4d19c6b9f026cc247c135b14a6fecf76a9fcd70 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 15:17:17 +0700 Subject: push --- indoteknik_api/controllers/api_v1/stock_picking.py | 6 +-- indoteknik_custom/models/stock_picking.py | 55 +++++++++++++++++++++- indoteknik_custom/views/stock_picking.xml | 2 + 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index 55e07152..a2cd0557 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//documentation', auth='public', methods=['PUT', 'OPTIONS'], csrf=False) + @http.route(prefix + 'stock-picking//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/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4e926e60..76ba51d4 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -76,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", ) @@ -216,6 +221,16 @@ class StockPicking(models.Model): # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) 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): @@ -1059,6 +1074,7 @@ class StockPicking(models.Model): 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: @@ -1088,6 +1104,26 @@ class StockPicking(models.Model): 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: @@ -1206,11 +1242,28 @@ class StockPicking(models.Model): def create(self, vals): self._use_faktur(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 diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 5424f3d3..f7b6134d 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -19,6 +19,7 @@ + - +
Pembayaran Lengkap: Pilih metode pembayaran yang sesuai dengan kebutuhan Anda, baik transfer bank, VA, kartu kredit, atau pembayaran tempo.
Pelacakan Pengiriman: Lacak status pengiriman pesanan Anda secara real-time.
Untuk memulai transaksi, silakan login Kembali menggunakan akun Anda di Indoteknik.com
Gunakan kode voucher berikut untuk mendapatkan diskon belanja hingga 10 juta: ${object.get_voucher_code('switch_account')}
Kami sangat senang dapat melayani Anda. Jika ada pertanyaan atau membutuhkan bantuan, jangan ragu untuk menghubungi tim layanan Customer kami di Whatsapp 0817-1718-1922 atau sales@indoteknik.com
Hormat kami,
Selamat! Akun bisnis Anda di indoteknik.com sekarang sudah resmi menjadi PKP dari yang sebelumnya Non-PKP.
Jangan lupa untuk mengecek kembali semua data perusahaan kamu, ya. Pastikan NPWP dan informasi Perusahaan lainnya sudah kamu isi dengan benar.
Gunakan kode voucher berikut untuk mendapatkan diskon belanja hingga 10 juta: ${object.get_voucher_code('switch_account')}
Kamu juga dapat mengubah informasi perusahaan dengan mengunjungi profil atau klik disini
Industrial Supply & Solutions
- +
- - - - + + +
- - +
Dear ${(object.partner_id.get_main_parent()).name},
+ + + + + + + + + + + +
+ + + + - - -
+ + + + - - - + + + + + + + + + - - - -
+ Dear + ${(object.partner_id.get_main_parent()).name},
Ini adalah konfirmasi pesanan dari ${object.partner_id.name | safe} untuk nomor pesanan ${object.name} yang memerlukan persetujuan agar dapat diproses.
- - Lihat Pesanan - -
Mohon segera melakukan tinjauan terhadap pesanan ini dan memberikan persetujuan. Terima kasih atas perhatian dan kerjasama Anda. Kami berharap dapat segera melanjutkan proses pesanan ini setelah mendapatkan persetujuan Anda.
Ini adalah + konfirmasi pesanan dari + ${object.partner_id.name | safe} untuk nomor + pesanan ${object.name} yang memerlukan + persetujuan agar dapat diproses.
+ + Lihat Pesanan + +
Mohon segera + melakukan tinjauan terhadap pesanan ini dan + memberikan persetujuan. Terima kasih atas + perhatian dan kerjasama Anda. Kami berharap + dapat segera melanjutkan proses pesanan ini + setelah mendapatkan persetujuan Anda.
Hormat kami,
PT. Indoteknik Dotcom Gemilang
sales@indoteknik.com
-
-
Hormat kami,
PT. Indoteknik + Dotcom Gemilang
+ sales@indoteknik.com
+
+
@@ -417,11 +503,11 @@ sales.order.purchase.match - - - - - + + + + + @@ -433,9 +519,9 @@ sales.order.koli - - - + + + @@ -444,32 +530,32 @@ - - sales.order.fulfillment.v2.tree - sales.order.fulfillment.v2 - - - - - - - - - - - - - + + sales.order.fulfillment.v2.tree + sales.order.fulfillment.v2 + + + + + + + + + + + + + sales.order.fullfillment.tree sales.order.fullfillment - - - - + + + + @@ -481,9 +567,9 @@ sales.order.reject - - - + + + @@ -492,8 +578,8 @@ Uang Muka - - + + code action = records.open_form_multi_create_uang_muka() @@ -502,66 +588,84 @@ Sale Order: Notification to Salesperson - + Konsolidasi Pengiriman sales@indoteknik.com ${object.user_id.login | safe} - - +
- - - - - - - - - - - - -
- - - - -
- -
-
- - - - - - - - - - - - -
Dear ${salesperson_name},
Terdapat pesanan dari BP ${business_partner} untuk site ${site} dengan total belanja ${sum_total_amount} dari list SO dibawah ini:
- - - - - - - - - - - ${table_content} - -
Nama PesananNama Perusahaan IndukNama SitusTotal Pembelian
-
-
-
-
-
+ + +
+ + + + + + + + + + + + +
+ + + + +
+ +
+
+ + + + + + + + + + + + + + + + +
Dear + ${salesperson_name},
Terdapat + pesanan dari BP ${business_partner} untuk + site ${site} dengan total belanja + ${sum_total_amount} dari list SO dibawah + ini:
+ + + + + + + + + + + ${table_content} + +
Nama PesananNama Perusahaan IndukNama SitusTotal Pembelian
+
+
+
+
+
-- cgit v1.2.3 From 71e47cc88e250600c2975a114dd420f27fda36b3 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Tue, 22 Apr 2025 12:45:44 +0700 Subject: (andri) change min dev amt from 5000 to 100 --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 92581678..37f767ec 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -704,7 +704,7 @@ class SaleOrder(models.Model): else: raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.') - if self.delivery_amt < 5000: + if self.delivery_amt < 100: if self.carrier_id.id == 1: raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.') else: -- cgit v1.2.3 From 9e867f016484f8695fce9e0c313d41010434390c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 22 Apr 2025 15:01:28 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 96aac7a1..cd038f44 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -26,11 +26,11 @@ _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) + konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True, copy=False) + scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True, copy=False) + check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True, copy=False) - check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) + check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, copy=False) barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') account_id = fields.Many2one('account.account', string='Account') @@ -100,7 +100,7 @@ class StockPicking(models.Model): ('pengajuan1', 'Approval Finance'), ('approved', 'Approved'), ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") - date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True) + date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, copy=False) note_logistic = fields.Selection([ ('hold', 'Hold by Sales'), ('not_paid', 'Customer belum bayar'), @@ -154,8 +154,8 @@ class StockPicking(models.Model): # 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') - linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out') + so_lama = fields.Boolean('SO LAMA', copy=False) + linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) # def write(self, vals): # if 'linked_manual_bu_out' in vals: -- cgit v1.2.3 From 30382037882e15bb43f56fc0d81500faeb364fa5 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 08:59:23 +0700 Subject: (andri) add field min amt tax vendor PO --- indoteknik_custom/models/purchase_order.py | 76 +++++++++++++++++++++++++ indoteknik_custom/models/res_partner.py | 4 ++ indoteknik_custom/views/vendor_payment_term.xml | 2 + 3 files changed, 82 insertions(+) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 5e9e509f..60d26105 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -122,6 +122,80 @@ class PurchaseOrder(models.Model): title="Payment Term Diperbarui" ) + def _check_tax_rule(self): + _logger.info("Check Tax Rule Terpanggil") + + # Pajak 11% + tax_11 = self.env['account.tax'].search([ + ('type_tax_use', '=', 'purchase'), + ('name', 'ilike', '11%') + ], limit=1) + + # Pajak "No Tax" + no_tax = self.env['account.tax'].search([ + ('type_tax_use', '=', 'purchase'), + ('name', 'ilike', 'no tax') + ], limit=1) + + if not tax_11: + raise UserError("Pajak 11% tidak ditemukan. Mohon pastikan pajak 11% tersedia.") + + if not no_tax: + raise UserError("Pajak 'No Tax' tidak ditemukan. Harap buat tax dengan nama 'No Tax' dan tipe 'Purchase'.") + + for order in self: + partner = order.partner_id + minimum_tax = partner.minimum_amount_tax + + _logger.info("Partner ID: %s, Minimum Tax: %s, Untaxed Total: %s", partner.id, minimum_tax, order.amount_untaxed) + + if not minimum_tax or not order.order_line: + continue + + if order.amount_untaxed < minimum_tax: + _logger.info(">>> Total di bawah minimum → apply No Tax") + for line in order.order_line: + line.taxes_id = [(6, 0, [no_tax.id])] + + if self.env.context.get('notify_tax'): + self.env.user.notify_info( + message="Total belanja PO belum mencapai minimum pajak vendor. " + "Pajak diganti menjadi 'No Tax'.", + title="Pajak Diperbarui", + sticky=True + ) + else: + _logger.info(">>> Total memenuhi minimum → apply Pajak 11%") + for line in order.order_line: + line.taxes_id = [(6, 0, [tax_11.id])] + + if self.env.context.get('notify_tax'): + self.env.user.notify_info( + message="Total belanja sebelum pajak telah memenuhi minimum. " + "Pajak 11%% diterapkan", + title="Pajak Diperbarui", + sticky=True + ) + + @api.onchange('order_line') + def _onchange_order_line_tax_default(self): + _logger.info("Onchange Order Line Tax Default Terpanggil") + + no_tax = self.env['account.tax'].search([ + ('type_tax_use', '=', 'purchase'), + ('name', 'ilike', 'no tax') + ], limit=1) + + if not no_tax: + _logger.info("No Tax tidak ditemukan") + return + + for order in self: + for line in order.order_line: + if not line.taxes_id: + line.taxes_id = [(6, 0, [no_tax.id])] + _logger.info("Auto-set No tax ke baris product: %s", line.product_id.name) + @api.onchange('total_cost_service') def _onchange_total_cost_service(self): for order in self: @@ -1117,12 +1191,14 @@ class PurchaseOrder(models.Model): def create(self, vals): order = super().create(vals) order.with_context(skip_check_payment=True)._check_payment_term() + order.with_context(notify_tax=True)._check_tax_rule() return order def write(self, vals): res = super().write(vals) if not self.env.context.get('skip_check_payment'): self.with_context(skip_check_payment=True)._check_payment_term() + self.with_context(notify_tax=True)._check_tax_rule() return res class PurchaseOrderUnlockWizard(models.TransientModel): diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 84edafea..191a44c9 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -44,6 +44,10 @@ class ResPartner(models.Model): string="Minimum Order", help="Jika total belanja kurang dari ini, maka payment term akan otomatis menjadi CBD." ) + minimum_amount_tax = fields.Float( + string="Minimum Amount Tax", + help="Jika total belanja kurang dari ini, maka tax akan otomatis menjadi 0%." + ) category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan', domain=lambda self: self._get_default_category_domain()) @api.model diff --git a/indoteknik_custom/views/vendor_payment_term.xml b/indoteknik_custom/views/vendor_payment_term.xml index f7c7e4dc..7d16b129 100644 --- a/indoteknik_custom/views/vendor_payment_term.xml +++ b/indoteknik_custom/views/vendor_payment_term.xml @@ -9,6 +9,7 @@ + @@ -25,6 +26,7 @@ + -- cgit v1.2.3 From 8334445b2946fc7383acb682ec5cde3598b8accc Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 09:01:21 +0700 Subject: (miqdad) fix duplicate voucher --- indoteknik_custom/models/voucher.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index 101d4bcf..7b458d01 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -265,3 +265,19 @@ class Voucher(models.Model): tnc.append(f'
  • {line_tnc}
  • ') return ' '.join(tnc) + # copy semua data kalau diduplicate + def copy(self, default=None): + default = dict(default or {}) + voucher_lines = [] + + for line in self.voucher_line: + voucher_lines.append((0, 0, { + 'manufacture_id': line.manufacture_id.id, + 'discount_amount': line.discount_amount, + 'discount_type': line.discount_type, + 'min_purchase_amount': line.min_purchase_amount, + 'max_discount_amount': line.max_discount_amount, + })) + + default['voucher_line'] = voucher_lines + return super(Voucher, self).copy(default) \ No newline at end of file -- cgit v1.2.3 From f3854677b0f74db2fbb54ad012b3a6a6fc9e11df Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 11:53:12 +0700 Subject: (andri) fix notify ganda pada PO --- indoteknik_custom/models/purchase_order.py | 39 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 60d26105..98b367d0 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -152,7 +152,7 @@ class PurchaseOrder(models.Model): if not minimum_tax or not order.order_line: continue - if order.amount_untaxed < minimum_tax: + if order.amount_total < minimum_tax: _logger.info(">>> Total di bawah minimum → apply No Tax") for line in order.order_line: line.taxes_id = [(6, 0, [no_tax.id])] @@ -162,7 +162,6 @@ class PurchaseOrder(models.Model): message="Total belanja PO belum mencapai minimum pajak vendor. " "Pajak diganti menjadi 'No Tax'.", title="Pajak Diperbarui", - sticky=True ) else: _logger.info(">>> Total memenuhi minimum → apply Pajak 11%") @@ -174,27 +173,27 @@ class PurchaseOrder(models.Model): message="Total belanja sebelum pajak telah memenuhi minimum. " "Pajak 11%% diterapkan", title="Pajak Diperbarui", - sticky=True ) - @api.onchange('order_line') - def _onchange_order_line_tax_default(self): - _logger.info("Onchange Order Line Tax Default Terpanggil") + # set default no_tax pada order line + # @api.onchange('order_line') + # def _onchange_order_line_tax_default(self): + # _logger.info("Onchange Order Line Tax Default Terpanggil") - no_tax = self.env['account.tax'].search([ - ('type_tax_use', '=', 'purchase'), - ('name', 'ilike', 'no tax') - ], limit=1) + # no_tax = self.env['account.tax'].search([ + # ('type_tax_use', '=', 'purchase'), + # ('name', 'ilike', 'no tax') + # ], limit=1) - if not no_tax: - _logger.info("No Tax tidak ditemukan") - return + # if not no_tax: + # _logger.info("No Tax tidak ditemukan") + # return - for order in self: - for line in order.order_line: - if not line.taxes_id: - line.taxes_id = [(6, 0, [no_tax.id])] - _logger.info("Auto-set No tax ke baris product: %s", line.product_id.name) + # for order in self: + # for line in order.order_line: + # if not line.taxes_id: + # line.taxes_id = [(6, 0, [no_tax.id])] + # _logger.info("Auto-set No tax ke baris product: %s", line.product_id.name) @api.onchange('total_cost_service') def _onchange_total_cost_service(self): @@ -1190,8 +1189,8 @@ class PurchaseOrder(models.Model): @api.model #override custom create & write for check payment term def create(self, vals): order = super().create(vals) - order.with_context(skip_check_payment=True)._check_payment_term() - order.with_context(notify_tax=True)._check_tax_rule() + # order.with_context(skip_check_payment=True)._check_payment_term() + # order.with_context(notify_tax=True)._check_tax_rule() return order def write(self, vals): -- cgit v1.2.3 From 1981b2d576d916374c181c8089655ab59a3f7f20 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 23 Apr 2025 14:29:06 +0700 Subject: barcode box --- indoteknik_custom/models/barcoding_product.py | 11 ++++-- indoteknik_custom/models/product_template.py | 2 ++ indoteknik_custom/models/stock_picking.py | 49 ++++++++++++++++++++------- indoteknik_custom/views/barcoding_product.xml | 5 +-- indoteknik_custom/views/product_template.xml | 2 ++ indoteknik_custom/views/stock_picking.xml | 2 +- 6 files changed, 52 insertions(+), 19 deletions(-) diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index e1b8f41f..17057d1d 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -12,15 +12,20 @@ class BarcodingProduct(models.Model): barcoding_product_line = fields.One2many('barcoding.product.line', 'barcoding_product_id', string='Barcoding Product Lines', auto_join=True) product_id = fields.Many2one('product.product', string="Product", tracking=3) quantity = fields.Float(string="Quantity", tracking=3) - type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product')], string='Type', default='print') + type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product')], string='Type', default='print') barcode = fields.Char(string="Barcode") + qty_pcs_box = fields.Char(string="Quantity Pcs Box") @api.constrains('barcode') def _send_barcode_to_product(self): for record in self: if record.barcode and not record.product_id.barcode: - record.product_id.barcode = record.barcode - + if record.type == 'barcoding_box': + record.product_id.barcode_box = record.barcode + record.product_id.qty_pcs_box = record.qty_pcs_box + else: + record.product_id.barcode = record.barcode + @api.onchange('product_id', 'quantity') def _onchange_product_or_quantity(self): """Update barcoding_product_line based on product_id and quantity""" diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 600dd90e..e6a01a04 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -421,6 +421,8 @@ class ProductProduct(models.Model): plafon_qty = fields.Float(string='Max Plafon', compute='_get_plafon_qty_product') merchandise_ok = fields.Boolean(string='Product Promotion') qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') + qty_pcs_box = fields.Float("Pcs Box") + barcode_box = fields.Char("Barcode Box") def generate_product_sla(self): product_variant_ids = self.env.context.get('active_ids', []) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cd038f44..6168d3b2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1621,21 +1621,44 @@ class CheckProduct(models.Model): copy=False, ) product_id = fields.Many2one('product.product', string='Product') - quantity = fields.Float(string='Quantity', default=1.0) + quantity = fields.Float(string='Quantity') status = fields.Char(string='Status', compute='_compute_status') code_product = fields.Char(string='Code Product') @api.onchange('code_product') def _onchange_code_product(self): - if self.code_product: - product = self.env['product.product'].search([('default_code', '=', self.code_product)], limit=1) - if not product: - product = self.env['product.product'].search([('barcode', '=', self.code_product)], limit=1) - - if product: - self.product_id = product.id - else: - raise UserError("Product tidak ditemukan") + if not self.code_product: + return + + # Cari product berdasarkan default_code, barcode, atau barcode_box + product = self.env['product.product'].search([ + '|', + ('default_code', '=', self.code_product), + '|', + ('barcode', '=', self.code_product), + ('barcode_box', '=', self.code_product) + ], limit=1) + + if not product: + raise UserError("Product tidak ditemukan") + + # Jika scan barcode_box, set quantity sesuai qty_pcs_box + if product.barcode_box == self.code_product: + self.product_id = product.id + self.quantity = product.qty_pcs_box + self.code_product = product.default_code or product.barcode + # return { + # 'warning': { + # 'title': 'Info',8994175025871 + + # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}' + # } + # } + else: + # Jika scan biasa + self.product_id = product.id + self.code_product = product.default_code or product.barcode + self.quantity = 1 def unlink(self): # Get all affected pickings before deletion @@ -1763,7 +1786,7 @@ class CheckProduct(models.Model): # Find existing lines for the same product, excluding the current line existing_lines = record.picking_id.check_product_lines.filtered( - lambda line: line.product_id == record.product_id and line.id != record.id + lambda line: line.product_id == record.product_id ) if existing_lines: @@ -1771,9 +1794,9 @@ class CheckProduct(models.Model): first_line = existing_lines[0] # Calculate the total quantity after addition - total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity + total_quantity = sum(existing_lines.mapped('quantity')) - if total_quantity == total_qty_in_moves: + if total_quantity > total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) diff --git a/indoteknik_custom/views/barcoding_product.xml b/indoteknik_custom/views/barcoding_product.xml index c7473d39..92064ee5 100644 --- a/indoteknik_custom/views/barcoding_product.xml +++ b/indoteknik_custom/views/barcoding_product.xml @@ -34,12 +34,13 @@ - + + - + diff --git a/indoteknik_custom/views/product_template.xml b/indoteknik_custom/views/product_template.xml index af21984a..076a8082 100755 --- a/indoteknik_custom/views/product_template.xml +++ b/indoteknik_custom/views/product_template.xml @@ -62,6 +62,8 @@ + + diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index da35d768..e99f8653 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -280,7 +280,7 @@ - + -- cgit v1.2.3 From 3f0a246d364a07f8c61eafeefcee7b37232a5933 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 14:57:14 +0700 Subject: (miqdad) rev add xpdc --- indoteknik_custom/models/sale_order.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 2061c686..bdb79fdf 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1438,19 +1438,6 @@ class SaleOrder(models.Model): # partner.sppkp = self.sppkp # partner.email = self.email - def _compute_total_margin(self): - for order in self: - total_margin = sum(line.item_margin for line in order.order_line if line.product_id) - #hitung nek onk - if order.ongkir_ke_xpdc: - total_margin -= order.ongkir_ke_xpdc - - order.total_margin = total_margin - - # def _compute_total_margin(self): - # for order in self: - # total_margin = sum(line.item_margin for line in order.order_line if line.product_id) - # order.total_margin = total_margin def _compute_total_percent_margin(self): for order in self: @@ -1768,4 +1755,13 @@ class SaleOrder(models.Model): 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 + return res + + def _compute_total_margin(self): + for order in self: + total_margin = sum(line.item_margin for line in order.order_line if line.product_id) + #hitung nek onk + if order.ongkir_ke_xpdc: + total_margin -= order.ongkir_ke_xpdc + + order.total_margin = total_margin \ No newline at end of file -- cgit v1.2.3 From 4c02839073b250bb46d1c3b927ad56b006636962 Mon Sep 17 00:00:00 2001 From: IT Fixcomart Date: Wed, 23 Apr 2025 08:08:37 +0000 Subject: sale_order.py edited online with Bitbucket --- indoteknik_custom/models/sale_order.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bdb79fdf..b4651f3c 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1437,7 +1437,15 @@ class SaleOrder(models.Model): # partner.npwp = self.npwp # partner.sppkp = self.sppkp # partner.email = self.email + + def _compute_total_margin(self): + for order in self: + total_margin = sum(line.item_margin for line in order.order_line if line.product_id) + #hitung nek onk + if order.ongkir_ke_xpdc: + total_margin -= order.ongkir_ke_xpdc + order.total_margin = total_margin def _compute_total_percent_margin(self): for order in self: @@ -1755,13 +1763,4 @@ class SaleOrder(models.Model): self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() - return res - - def _compute_total_margin(self): - for order in self: - total_margin = sum(line.item_margin for line in order.order_line if line.product_id) - #hitung nek onk - if order.ongkir_ke_xpdc: - total_margin -= order.ongkir_ke_xpdc - - order.total_margin = total_margin \ No newline at end of file + return res \ No newline at end of file -- cgit v1.2.3 From 57d299950d11976917fe516736afeb0f3e0233a3 Mon Sep 17 00:00:00 2001 From: IT Fixcomart Date: Wed, 23 Apr 2025 08:16:37 +0000 Subject: sale_order.xml edited online with Bitbucket --- indoteknik_custom/views/sale_order.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 79a095fb..2ed35381 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -34,6 +34,7 @@ attrs="{'required': ['|', ('create_date', '>', '2023-06-15'), ('create_date', '=', False)]}" /> + -- cgit v1.2.3 From 9e9fef462d5fb71de37bb7a4a190d316af394b10 Mon Sep 17 00:00:00 2001 From: IT Fixcomart Date: Wed, 23 Apr 2025 08:18:11 +0000 Subject: xpdc views --- indoteknik_custom/views/sale_order.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 2ed35381..79a095fb 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -34,7 +34,6 @@ attrs="{'required': ['|', ('create_date', '>', '2023-06-15'), ('create_date', '=', False)]}" /> - -- cgit v1.2.3 From f710d62fcb33e43a832d18b28baa4641f9fb65a8 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 15:39:46 +0700 Subject: (miqdad) Fix conflict Xpdc --- indoteknik_custom/models/sale_order.py | 76 ++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bdb79fdf..6b603146 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -326,7 +326,17 @@ class SaleOrder(models.Model): "sale_order_id": self.id, }) self.shipping_option_id = shipping_option.id - + self.message_post( + body=( + f"Estimasi pengiriman Indoteknik berhasil:
    " + f"Layanan: {shipping_option.name}
    " + f"ETD: {shipping_option.etd}
    " + f"Biaya: Rp {shipping_option.price:,}
    " + f"Provider: {shipping_option.provider}" + ), + message_type="comment", + ) + def action_estimate_shipping(self): if self.carrier_id.id in [1, 151]: self.action_indoteknik_estimate_shipping() @@ -365,6 +375,8 @@ class SaleOrder(models.Model): shipping_options.append((service, description, etd, value, courier['code'])) self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() + + _logger.info(f"Shipping options: {shipping_options}") for service, description, etd, value, provider in shipping_options: self.env["shipping.option"].create({ @@ -374,12 +386,23 @@ class SaleOrder(models.Model): "etd": etd, "sale_order_id": self.id, }) + self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id - self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
    Detail Lain:
    {'
    '.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}") + _logger.info(f"Shipping option SO ID: {self.shipping_option_id}") + + self.message_post( + body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
    Detail Lain:
    " + f"{'
    '.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", + message_type="comment" + ) + + # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
    Detail Lain:
    {'
    '.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment") + else: raise UserError("Gagal mendapatkan estimasi ongkir.") + def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' @@ -483,7 +506,7 @@ class SaleOrder(models.Model): def _compute_date_kirim(self): for rec in self: - picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')], order='date_doc_kirim desc', limit=1) + picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel'])], order='date_doc_kirim desc', limit=1) rec.date_kirim_ril = picking.date_doc_kirim rec.date_status_done = picking.date_done rec.date_driver_arrival = picking.driver_arrival_date @@ -682,12 +705,30 @@ class SaleOrder(models.Model): # @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') 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') + is_indoteknik = self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik' + is_active_id = not self.env.context.get('active_id', []) + + if is_indoteknik and is_active_id: + if self.delivery_amt == 0: + if self.carrier_id.id == 1: + raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum diisi.') + else: + raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.') + + if self.delivery_amt < 100: + if self.carrier_id.id == 1: + raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.') else: - raise UserError('Untuk Shipping Covered Indoteknik, Estimasi Ongkos Kirim Harus di isi') + raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.') + + + # if self.delivery_amt < 5000: + # 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 belum memenuhi jumlah minimum.') + # else: + # raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi jumlah minimum.') + def override_allow_create_invoice(self): if not self.env.user.is_accounting: @@ -1437,7 +1478,15 @@ class SaleOrder(models.Model): # partner.npwp = self.npwp # partner.sppkp = self.sppkp # partner.email = self.email + + def _compute_total_margin(self): + for order in self: + total_margin = sum(line.item_margin for line in order.order_line if line.product_id) + #hitung nek onk + if order.ongkir_ke_xpdc: + total_margin -= order.ongkir_ke_xpdc + order.total_margin = total_margin def _compute_total_percent_margin(self): for order in self: @@ -1755,13 +1804,4 @@ class SaleOrder(models.Model): self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() - return res - - def _compute_total_margin(self): - for order in self: - total_margin = sum(line.item_margin for line in order.order_line if line.product_id) - #hitung nek onk - if order.ongkir_ke_xpdc: - total_margin -= order.ongkir_ke_xpdc - - order.total_margin = total_margin \ No newline at end of file + return res \ No newline at end of file -- cgit v1.2.3 From 5f78d57ced0957b5f05e590de67a4b966b22e85b Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 15:46:28 +0700 Subject: (miqdad) fix conflict Xpdc --- indoteknik_custom/models/sale_order.py | 1 - 1 file changed, 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 6b603146..13646847 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1482,7 +1482,6 @@ class SaleOrder(models.Model): def _compute_total_margin(self): for order in self: total_margin = sum(line.item_margin for line in order.order_line if line.product_id) - #hitung nek onk if order.ongkir_ke_xpdc: total_margin -= order.ongkir_ke_xpdc -- cgit v1.2.3 From beb2ef24d462075dc390018afe1127db313fb404 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 10:39:19 +0700 Subject: push --- indoteknik_custom/models/barcoding_product.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index 17057d1d..353f94d5 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -19,12 +19,11 @@ class BarcodingProduct(models.Model): @api.constrains('barcode') def _send_barcode_to_product(self): for record in self: - if record.barcode and not record.product_id.barcode: - if record.type == 'barcoding_box': - record.product_id.barcode_box = record.barcode - record.product_id.qty_pcs_box = record.qty_pcs_box - else: - record.product_id.barcode = record.barcode + if record.type == 'barcoding_box': + record.product_id.barcode_box = record.barcode + record.product_id.qty_pcs_box = record.qty_pcs_box + else: + record.product_id.barcode = record.barcode @api.onchange('product_id', 'quantity') def _onchange_product_or_quantity(self): -- cgit v1.2.3 From 4706b80d3d3b1e55c198d2b4cfb93f7fa47c9732 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 13:49:35 +0700 Subject: validation duplicate barcode product and barcode box, cr date doc kirim, validation duplicate product id on so line --- indoteknik_custom/models/approval_date_doc.py | 9 ++++++--- indoteknik_custom/models/barcoding_product.py | 13 +++++++++++++ indoteknik_custom/models/sale_order.py | 10 ++++++++++ indoteknik_custom/models/stock_picking.py | 12 ++++++------ indoteknik_custom/views/stock_picking.xml | 1 + 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/indoteknik_custom/models/approval_date_doc.py b/indoteknik_custom/models/approval_date_doc.py index 751bae82..1a2749d5 100644 --- a/indoteknik_custom/models/approval_date_doc.py +++ b/indoteknik_custom/models/approval_date_doc.py @@ -39,12 +39,15 @@ class ApprovalDateDoc(models.Model): if not self.env.user.is_accounting: raise UserError("Hanya Accounting Yang Bisa Approve") self.check_invoice_so_picking - self.picking_id.driver_departure_date = self.driver_departure_date - self.picking_id.date_doc_kirim = self.driver_departure_date + # Tambahkan context saat mengupdate date_doc_kirim + self.picking_id.with_context(from_button_approve=True).write({ + 'driver_departure_date': self.driver_departure_date, + 'date_doc_kirim': self.driver_departure_date + }) self.state = 'done' self.approve_date = datetime.utcnow() self.approve_by = self.env.user.id - + def button_cancel(self): self.state = 'cancel' diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index 353f94d5..e28473ff 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -16,9 +16,22 @@ class BarcodingProduct(models.Model): barcode = fields.Char(string="Barcode") qty_pcs_box = fields.Char(string="Quantity Pcs Box") + def check_duplicate_barcode(self): + barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)]) + + if barcode_product: + raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) + + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) + + if barcode_box: + raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name)) + + @api.constrains('barcode') def _send_barcode_to_product(self): for record in self: + record.check_duplicate_barcode() if record.type == 'barcoding_box': record.product_id.barcode_box = record.barcode record.product_id.qty_pcs_box = record.qty_pcs_box diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 39d6fd0b..02d61387 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1069,7 +1069,16 @@ class SaleOrder(models.Model): 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 check_duplicate_product(self): + for order in self: + for line in order.order_line: + search_product = self.env['sale.order.line'].search([('product_id', '=', line.product_id.id), ('order_id', '=', order.id)]) + if len(search_product) > 1: + raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name)) + def sale_order_approve(self): + self.check_duplicate_product() self.check_product_bom() self.check_credit_limit() self.check_limit_so_to_invoice() @@ -1310,6 +1319,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: + order.check_duplicate_product() order.check_product_bom() order.check_credit_limit() order.check_limit_so_to_invoice() diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6168d3b2..f812df86 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -102,10 +102,9 @@ class StockPicking(models.Model): ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, copy=False) note_logistic = fields.Selection([ - ('hold', 'Hold by Sales'), + ('wait_so_together', 'Tunggu SO Barengan'), ('not_paid', 'Customer belum bayar'), - ('partial', 'Kirim Parsial'), - ('indent', 'Indent'), + ('reserve_stock', 'Reserve Stock'), ('waiting_schedule', 'Menunggu Jadwal Kirim'), ('self_pickup', 'Barang belum di pickup Customer'), ('expedition_closed', 'Eskpedisi belum buka') @@ -141,7 +140,8 @@ class StockPicking(models.Model): ('done', 'Done'), ('cancel', 'Cancelled'), ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") - notee = fields.Text(string="Note") + notee = fields.Text(string="Note SJ", help="Catatan untuk kirim barang") + note_info = fields.Text(string="Note", help="Catatan untuk pengiriman") state_approve_md = fields.Selection([ ('waiting', 'Waiting For Approve by MD'), ('pending', 'Pending (perlu koordinasi dengan MD)'), @@ -253,7 +253,7 @@ class StockPicking(models.Model): def _check_date_doc_kirim_modification(self): for record in self: - if record.last_update_date_doc_kirim: + if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'): kirim_date = fields.Datetime.from_string(record.last_update_date_doc_kirim) now = fields.Datetime.now() @@ -275,7 +275,7 @@ class StockPicking(models.Model): if invoice and not self.env.context.get('active_model') == 'stock.picking': rec._check_date_doc_kirim_modification() - if rec.date_doc_kirim != invoice.invoice_date: + if rec.date_doc_kirim != invoice.invoice_date and not self.env.context.get('from_button_approve'): get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index e99f8653..dd319bea 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -173,6 +173,7 @@ + -- cgit v1.2.3 From add5cafc05aec0036fa1382c90dbf3abc8e1ecf5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 13:52:08 +0700 Subject: add validation barcode product --- indoteknik_custom/models/stock_picking.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f812df86..a8a4a4e6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1826,9 +1826,21 @@ class BarcodeProduct(models.Model): product_id = fields.Many2one('product.product', string='Product', required=True) barcode = fields.Char(string='Barcode') + def check_duplicate_barcode(self): + barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)]) + + if barcode_product: + raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) + + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) + + if barcode_box: + raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name)) + @api.constrains('barcode') def send_barcode_to_product(self): for record in self: + record.check_duplicate_barcode() if record.barcode and not record.product_id.barcode: record.product_id.barcode = record.barcode else: -- cgit v1.2.3 From 21653f522154475d9029c1e2b395960b58ecf47a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 13:56:11 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- indoteknik_custom/views/stock_picking.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a8a4a4e6..8f8ab54d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -141,7 +141,7 @@ 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 SJ", help="Catatan untuk kirim barang") - note_info = fields.Text(string="Note", help="Catatan untuk pengiriman") + note_info = fields.Text(string="Note Logistix (Text)", help="Catatan untuk pengiriman") state_approve_md = fields.Selection([ ('waiting', 'Waiting For Approve by MD'), ('pending', 'Pending (perlu koordinasi dengan MD)'), diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index dd319bea..c916f2ef 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -173,9 +173,9 @@ - + -- cgit v1.2.3 From 2db8d058d5b7c291669240df90afc0312d509939 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 14:29:33 +0700 Subject: fix bug --- indoteknik_custom/models/barcoding_product.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index e28473ff..204c6128 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -17,16 +17,16 @@ class BarcodingProduct(models.Model): qty_pcs_box = fields.Char(string="Quantity Pcs Box") def check_duplicate_barcode(self): - barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)]) + if self.type in ['barcoding_box', 'barcoding']: + barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)]) - if barcode_product: - raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) - - barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) - - if barcode_box: - raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name)) + if barcode_product: + raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) + + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) + if barcode_box: + raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name)) @api.constrains('barcode') def _send_barcode_to_product(self): -- cgit v1.2.3 From c9b2981fbe94d045344cda2fd8f0ed3ed2becd54 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 15:36:27 +0700 Subject: push --- indoteknik_custom/views/barcoding_product.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/views/barcoding_product.xml b/indoteknik_custom/views/barcoding_product.xml index 92064ee5..55876580 100644 --- a/indoteknik_custom/views/barcoding_product.xml +++ b/indoteknik_custom/views/barcoding_product.xml @@ -36,7 +36,7 @@ - + -- cgit v1.2.3 From d6516bce4ac05a25baf060d5341f7b603961496f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 25 Apr 2025 10:39:05 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 8f8ab54d..e0ae8258 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1191,7 +1191,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.date_doc_kirim or not invoice.invoice_date: + if not picking.date_doc_kirim or not invoice.invoice_date and not picking.so_lama: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From 6dfa0b1ec5c9fd60f2adee26b7dbb1de3aea5302 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 25 Apr 2025 16:11:46 +0700 Subject: push --- indoteknik_custom/models/approval_date_doc.py | 3 ++- indoteknik_custom/models/stock_picking.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/approval_date_doc.py b/indoteknik_custom/models/approval_date_doc.py index 1a2749d5..638b44d7 100644 --- a/indoteknik_custom/models/approval_date_doc.py +++ b/indoteknik_custom/models/approval_date_doc.py @@ -42,7 +42,8 @@ class ApprovalDateDoc(models.Model): # Tambahkan context saat mengupdate date_doc_kirim self.picking_id.with_context(from_button_approve=True).write({ 'driver_departure_date': self.driver_departure_date, - 'date_doc_kirim': self.driver_departure_date + 'date_doc_kirim': self.driver_departure_date, + 'update_date_doc_kirim_add': True }) self.state = 'done' self.approve_date = datetime.utcnow() diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index e0ae8258..64dc1499 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -250,6 +250,7 @@ class StockPicking(models.Model): state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') + update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') def _check_date_doc_kirim_modification(self): for record in self: @@ -1197,7 +1198,7 @@ class StockPicking(models.Model): picking_date = fields.Date.to_date(picking.date_doc_kirim) invoice_date = fields.Date.to_date(invoice.invoice_date) - if picking_date != invoice_date: + if picking_date != invoice_date and picking.update_date_doc_kirim_add: raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % ( picking_date.strftime('%d-%m-%Y'), invoice_date.strftime('%d-%m-%Y') -- cgit v1.2.3 From 6dcc5e48adb87d43101eaa2e868d12356da092be Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 26 Apr 2025 09:50:42 +0700 Subject: Removed product from order line moved to reject line --- indoteknik_custom/models/sale_order.py | 49 ++++++++++++++++++++++++++ indoteknik_custom/models/sales_order_reject.py | 27 ++++++++++++++ indoteknik_custom/views/sale_order.xml | 4 +-- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 02d61387..d2c49bf0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -64,6 +64,55 @@ class ShippingOption(models.Model): etd = fields.Char(string="Estimated Delivery Time") sale_order_id = fields.Many2one('sale.order', string="Sale Order", ondelete="cascade") +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + def unlink(self): + lines_to_reject = [] + for line in self: + if line.order_id: + now = fields.Datetime.now() + + initial_reason="Product Rejected" + + # Buat lognote untuk product yang di delete + log_note = (f"
  • Product '{line.product_id.name}' rejected.
  • " + f"
  • Quantity: {line.product_uom_qty},
  • " + f"
  • Date: {now.strftime('%d-%m-%Y')},
  • " + f"
  • Time: {now.strftime('%H:%M:%S')}
  • " + f"
  • Reason reject: {initial_reason}
  • ") + + lines_to_reject.append({ + 'sale_order_id': line.order_id.id, + 'product_id': line.product_id.id, + 'qty_reject': line.product_uom_qty, + 'reason_reject': initial_reason, # pesan reason reject + 'message_body': log_note, + 'order_id': line.order_id, + }) + + # Call the original unlink method + result = super(SaleOrderLine, self).unlink() + + # After deletion, create reject lines and post messages + SalesOrderReject = self.env['sales.order.reject'] + for reject_data in lines_to_reject: + # Buat line baru di reject line + SalesOrderReject.create({ + 'sale_order_id': reject_data['sale_order_id'], + 'product_id': reject_data['product_id'], + 'qty_reject': reject_data['qty_reject'], + 'reason_reject': reject_data['reason_reject'], + }) + + # Post to chatter with a more prominent message + reject_data['order_id'].message_post( + body=reject_data['message_body'], + author_id=self.env.user.partner_id.id, # menampilkan pesan di lognote sebagai current user + ) + + return result + class SaleOrder(models.Model): _inherit = "sale.order" diff --git a/indoteknik_custom/models/sales_order_reject.py b/indoteknik_custom/models/sales_order_reject.py index 9983c64e..b180fad6 100644 --- a/indoteknik_custom/models/sales_order_reject.py +++ b/indoteknik_custom/models/sales_order_reject.py @@ -13,3 +13,30 @@ class SalesOrderReject(models.Model): product_id = fields.Many2one('product.product', string='Product') qty_reject = fields.Float(string='Qty') reason_reject = fields.Char(string='Reason Reject') + + def write(self, vals): + # Check if reason_reject is being updated + if 'reason_reject' in vals: + for record in self: + old_reason = record.reason_reject + new_reason = vals['reason_reject'] + + # Only post a message if the reason actually changed + if old_reason != new_reason: + now = fields.Datetime.now() + + # Create the log note for the updated reason + log_note = (f"
  • Product '{record.product_id.name}' rejection reason updated:
  • " + f"
  • From: {old_reason}
  • " + f"
  • To: {new_reason}
  • " + f"
  • Updated on: {now.strftime('%d-%m-%Y')} at {now.strftime('%H:%M:%S')}
  • ") + + # Post ke lognote + if record.sale_order_id: + record.sale_order_id.message_post( + body=log_note, + author_id=self.env.user.partner_id.id, + ) + + # Call the original write method + return super(SalesOrderReject, self).write(vals) \ No newline at end of file diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 79a095fb..10c60e24 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -334,7 +334,7 @@
    - + @@ -569,7 +569,7 @@ - + -- cgit v1.2.3 From 8b532b193a47c6be35bcfb49b77849458a379cee Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 28 Apr 2025 09:34:12 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 64dc1499..a6b3373f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1192,7 +1192,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.date_doc_kirim or not invoice.invoice_date and not picking.so_lama: + if picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From bac1744ce4e27d796fd2b52f5fbcd3d5cdabdc75 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 28 Apr 2025 09:35:23 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a6b3373f..d2b1b9f2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1192,7 +1192,7 @@ class StockPicking(models.Model): if not invoice: continue - if picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: + if not picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From a2a05e1183f9829b09d2e065815cb3618cc05049 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 28 Apr 2025 09:43:02 +0700 Subject: (andri) rev fix margin approval untuk 1 Mei --- indoteknik_custom/models/sale_order.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 02d61387..ab63509b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1451,11 +1451,13 @@ class SaleOrder(models.Model): due_extension.unlink() return False + # Rule: # ≤ 15% → Pimpinan, > 15% - <24% → Manager, == 24% → Langsung Confirm def _requires_approval_margin_leader(self): - return self.total_percent_margin < 15 and not self.env.user.is_leader + return self.total_percent_margin <= 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager + return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader + # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 082a4cf4c1f109644cb5c7a45ed1d3ffa69b1b06 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 28 Apr 2025 14:42:51 +0700 Subject: (andri) Add validation: BOM cannot be mark done if SO has not been confirmed --- indoteknik_custom/models/manufacturing.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/indoteknik_custom/models/manufacturing.py b/indoteknik_custom/models/manufacturing.py index 24a8b8c3..715d8513 100644 --- a/indoteknik_custom/models/manufacturing.py +++ b/indoteknik_custom/models/manufacturing.py @@ -26,6 +26,13 @@ class Manufacturing(models.Model): # Check product category if self.product_id.categ_id.name != 'Finish Good': raise UserError('Tidak bisa di complete karna product category bukan Unit / Finish Good') + + if self.sale_order and self.sale_order.state != 'sale': + raise UserError( + ('Tidak bisa Mark as Done.\nSales Order "%s" (Nomor: %s) belum dikonfirmasi.') + % (self.sale_order.partner_id.name, self.sale_order.name) + ) + for line in self.move_raw_ids: # if line.quantity_done > 0 and line.quantity_done != self.product_uom_qty: # raise UserError('Qty Consume per Line tidak sama dengan Qty to Produce') -- cgit v1.2.3 From f2579dfcf01ab1687deb7221b50129fadb95d671 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 28 Apr 2025 15:03:38 +0700 Subject: (andri) undo fix margin --- indoteknik_custom/models/sale_order.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index ab63509b..02d61387 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1451,13 +1451,11 @@ class SaleOrder(models.Model): due_extension.unlink() return False - # Rule: # ≤ 15% → Pimpinan, > 15% - <24% → Manager, == 24% → Langsung Confirm def _requires_approval_margin_leader(self): - return self.total_percent_margin <= 15 and not self.env.user.is_leader + return self.total_percent_margin < 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader - # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager + return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 783b674e04dd123a5233fd01896925c73aa8143c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 29 Apr 2025 10:00:15 +0700 Subject: check product on bom, view stock picking po and fix bug api flashsale header --- indoteknik_api/controllers/api_v1/flash_sale.py | 2 +- indoteknik_api/models/product_pricelist.py | 9 +- indoteknik_custom/models/mrp_production.py | 284 ++++++++++++++++++++++++ indoteknik_custom/models/stock_move.py | 15 ++ indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/mrp_production.xml | 16 ++ 6 files changed, 324 insertions(+), 3 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/flash_sale.py b/indoteknik_api/controllers/api_v1/flash_sale.py index 6c4ad8c0..1038500c 100644 --- a/indoteknik_api/controllers/api_v1/flash_sale.py +++ b/indoteknik_api/controllers/api_v1/flash_sale.py @@ -14,7 +14,7 @@ class FlashSale(controller.Controller): def _get_flash_sale_header(self, **kw): try: # base_url = request.env['ir.config_parameter'].get_param('web.base.url') - active_flash_sale = request.env['product.pricelist'].get_is_show_program_flash_sale() + active_flash_sale = request.env['product.pricelist'].get_is_show_program_flash_sale(is_show_program=kw.get('is_show_program')) data = [] for pricelist in active_flash_sale: query = [ diff --git a/indoteknik_api/models/product_pricelist.py b/indoteknik_api/models/product_pricelist.py index 6e88517c..2e825740 100644 --- a/indoteknik_api/models/product_pricelist.py +++ b/indoteknik_api/models/product_pricelist.py @@ -95,15 +95,20 @@ class ProductPricelist(models.Model): ], limit=1, order='start_date asc') return pricelist - def get_is_show_program_flash_sale(self): + def get_is_show_program_flash_sale(self, is_show_program): """ Check whether have active flash sale in range of date @return: returns pricelist: object """ + + if is_show_program == 'true': + is_show_program = True + else: + is_show_program = False current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') pricelist = self.search([ ('is_flash_sale', '=', True), - ('is_show_program', '=', True), + ('is_show_program', '=', is_show_program), ('start_date', '<=', current_time), ('end_date', '>=', current_time) ], order='start_date asc') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index d80df2ce..ebbd1c24 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -8,11 +8,34 @@ from odoo.exceptions import AccessError, UserError, ValidationError class MrpProduction(models.Model): _inherit = 'mrp.production' + check_bom_product_lines = fields.One2many('check.bom.product', 'production_id', string='Check Product', auto_join=True, copy=False) 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') + @api.constrains('check_bom_product_lines') + def constrains_check_bom_product_lines(self): + for rec in self: + if len(rec.check_bom_product_lines) > 0: + rec.qty_producing = rec.product_qty + + def button_mark_done(self): + """Override button_mark_done untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'.""" + if self._name != 'mrp.production': + return super(MrpProduction, self).button_mark_done() + + result = super(MrpProduction, self).button_mark_done() + + for record in self: + if len(record.check_bom_product_lines) < 1: + raise UserError("Check Product Tidak Boleh Kosong") + if record.sale_order and record.state == 'confirmed': + message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name) + record.sale_order.message_post(body=message) + + return result + def action_confirm(self): """Override action_confirm untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'.""" if self._name != 'mrp.production': @@ -21,6 +44,8 @@ class MrpProduction(models.Model): result = super(MrpProduction, self).action_confirm() for record in self: + # if len(record.check_bom_product_lines) < 1: + # raise UserError("Check Product Tidak Boleh Kosong") if record.sale_order and record.state == 'confirmed': message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name) record.sale_order.message_post(body=message) @@ -171,6 +196,265 @@ class MrpProduction(models.Model): return price, taxes, vendor_id +class CheckBomProduct(models.Model): + _name = 'check.bom.product' + _description = 'Check Product' + _order = 'production_id, id' + + production_id = fields.Many2one( + 'mrp.production', + string='Bom Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + product_id = fields.Many2one('product.product', string='Product') + quantity = fields.Float(string='Quantity') + status = fields.Char(string='Status', compute='_compute_status') + code_product = fields.Char(string='Code Product') + + @api.constrains('production_id') + def _check_missing_components(self): + for mo in self: + required = mo.production_id.move_raw_ids.mapped('product_id') + entered = mo.production_id.check_bom_product_lines.mapped('product_id') + missing = required - entered + + # Jika HTML tidak bekerja sama sekali, gunakan format text biasa yang rapi + if missing: + product_list = "\n- " + "\n- ".join(p.display_name for p in missing) + raise UserError( + "⚠️ Komponen Wajib Diisi\n\n" + "Produk berikut harus ditambahkan:\n" + f"{product_list}\n\n" + "Silakan lengkapi terlebih dahulu." + ) + + @api.constrains('production_id', 'product_id') + def _check_product_bom_validation(self): + for record in self: + if not record.production_id or not record.product_id: + continue + + moves = record.production_id.move_raw_ids.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + + if not moves: + raise UserError(( + "The product '%s' tidak ada di operations. " + ) % record.product_id.display_name) + + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + # Find existing lines for the same product, excluding the current line + existing_lines = record.production_id.check_bom_product_lines.filtered( + lambda line: line.product_id == record.product_id + ) + + if existing_lines: + total_quantity = sum(existing_lines.mapped('quantity')) + + if total_quantity < total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' kurang dari quantity demand." + ) % (record.product_id.display_name)) + else: + # Check if the quantity exceeds the allowed total + if record.quantity < total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' kurang dari quantity demand." + ) % (record.product_id.display_name)) + + # Set the quantity to the entered value + record.quantity = record.quantity + + @api.onchange('code_product') + def _onchange_code_product(self): + if not self.code_product: + return + + # Cari product berdasarkan default_code, barcode, atau barcode_box + product = self.env['product.product'].search([ + '|', + ('default_code', '=', self.code_product), + '|', + ('barcode', '=', self.code_product), + ('barcode_box', '=', self.code_product) + ], limit=1) + + if not product: + raise UserError("Product tidak ditemukan") + + # Jika scan barcode_box, set quantity sesuai qty_pcs_box + if product.barcode_box == self.code_product: + self.product_id = product.id + self.quantity = product.qty_pcs_box + self.code_product = product.default_code or product.barcode + # return { + # 'warning': { + # 'title': 'Info',8994175025871 + + # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}' + # } + # } + else: + # Jika scan biasa + self.product_id = product.id + self.code_product = product.default_code or product.barcode + self.quantity = 1 + + def unlink(self): + # Get all affected pickings before deletion + productions = self.mapped('production_id') + + # Store product_ids that will be deleted + deleted_product_ids = self.mapped('product_id') + + # Perform the deletion + result = super(CheckBomProduct, self).unlink() + + # After deletion, update moves for affected pickings + for production in productions: + # For products that were completely removed (no remaining check.bom.product lines) + remaining_product_ids = production.check_bom_product_lines.mapped('product_id') + removed_product_ids = deleted_product_ids - remaining_product_ids + + # Set quantity_done to 0 for moves of completely removed products + moves_to_reset = production.move_raw_ids.filtered( + lambda move: move.product_id in removed_product_ids + ) + for move in moves_to_reset: + move.quantity_done = 0.0 + + # Also sync remaining products in case their totals changed + self._sync_check_product_to_moves(production) + + return result + + @api.depends('quantity') + def _compute_status(self): + for record in self: + moves = record.production_id.move_raw_ids.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + if record.quantity < total_qty_in_moves: + record.status = 'Pending' + else: + record.status = 'Done' + + + def create(self, vals): + # Create the record + record = super(CheckBomProduct, self).create(vals) + # Ensure uniqueness after creation + if not self.env.context.get('skip_consolidate'): + record.with_context(skip_consolidate=True)._consolidate_duplicate_lines() + return record + + def write(self, vals): + # Write changes to the record + result = super(CheckBomProduct, self).write(vals) + # Ensure uniqueness after writing + if not self.env.context.get('skip_consolidate'): + self.with_context(skip_consolidate=True)._consolidate_duplicate_lines() + return result + + def _sync_check_product_to_moves(self, production): + """ + Sinkronisasi quantity_done di move_raw_ids + dengan total quantity dari check.bom.product berdasarkan product_id. + """ + for product_id in production.check_bom_product_lines.mapped('product_id'): + # Totalkan quantity dari semua baris check.bom.product untuk product_id ini + total_quantity = sum( + line.quantity for line in production.check_bom_product_lines.filtered(lambda line: line.product_id == product_id) + ) + # Update quantity_done di move yang relevan + moves = production.move_raw_ids.filtered(lambda move: move.product_id == product_id) + for move in moves: + move.quantity_done = total_quantity + + def _consolidate_duplicate_lines(self): + """ + Consolidate duplicate lines with the same product_id under the same production_id + and sync the total quantity to related moves. + """ + for production in self.mapped('production_id'): + lines_to_remove = self.env['check.bom.product'] # Recordset untuk menyimpan baris yang akan dihapus + product_lines = production.check_bom_product_lines.filtered(lambda line: line.product_id) + + # Group lines by product_id + product_groups = {} + for line in product_lines: + product_groups.setdefault(line.product_id.id, []).append(line) + + for product_id, lines in product_groups.items(): + if len(lines) > 1: + # Consolidate duplicate lines + first_line = lines[0] + total_quantity = sum(line.quantity for line in lines) + + # Update the first line's quantity + first_line.with_context(skip_consolidate=True).write({'quantity': total_quantity}) + + # Add the remaining lines to the lines_to_remove recordset + lines_to_remove |= self.env['check.bom.product'].browse([line.id for line in lines[1:]]) + + # Perform unlink after consolidation + if lines_to_remove: + lines_to_remove.unlink() + + # Sync total quantities to moves + self._sync_check_product_to_moves(production) + + @api.onchange('product_id', 'quantity') + def check_product_validity(self): + for record in self: + if not record.production_id or not record.product_id: + continue + + # Filter moves related to the selected product + moves = record.production_id.move_raw_ids.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + + if not moves: + raise UserError(( + "The product '%s' tidak ada di operations. " + ) % record.product_id.display_name) + + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + # Find existing lines for the same product, excluding the current line + existing_lines = record.production_id.check_bom_product_lines.filtered( + lambda line: line.product_id == record.product_id + ) + + if existing_lines: + # Get the first existing line + first_line = existing_lines[0] + + # Calculate the total quantity after addition + total_quantity = sum(existing_lines.mapped('quantity')) + + if total_quantity > total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) + else: + # Check if the quantity exceeds the allowed total + if record.quantity == total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) + + # Set the quantity to the entered value + record.quantity = record.quantity + class ProductionPurchaseMatch(models.Model): _name = 'production.purchase.match' diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 514acad0..e75c75f0 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -15,6 +15,21 @@ class StockMove(models.Model): barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') + @api.model_create_multi + def create(self, vals_list): + moves = super(StockMove, self).create(vals_list) + + for move in moves: + if move.product_id and move.location_id.id == 58 and move.location_dest_id.id == 57 and move.picking_type_id.id == 75: + po_line = self.env['purchase.order.line'].search([ + ('product_id', '=', move.product_id.id), + ('order_id.name', '=', move.origin) + ], limit=1) + if po_line: + move.write({'purchase_line_id': po_line.id}) + + return moves + @api.constrains('product_id') def constrains_product_to_fill_vendor(self): for rec in self: diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 30088680..24b8dfff 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -154,6 +154,7 @@ 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_bom_product,access.check.bom.product,model_check_bom_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 diff --git a/indoteknik_custom/views/mrp_production.xml b/indoteknik_custom/views/mrp_production.xml index f8278f39..419737f9 100644 --- a/indoteknik_custom/views/mrp_production.xml +++ b/indoteknik_custom/views/mrp_production.xml @@ -20,10 +20,26 @@ + + +
    + + check.bom.product.tree + check.bom.product + + + + + + + + + + mrp.production.view.inherited mrp.production -- cgit v1.2.3 From 47e59c2ec810bb6e77180ea4e9896f9c98f246fc Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 29 Apr 2025 10:22:25 +0700 Subject: push --- indoteknik_api/models/product_pricelist.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indoteknik_api/models/product_pricelist.py b/indoteknik_api/models/product_pricelist.py index 2e825740..744faaee 100644 --- a/indoteknik_api/models/product_pricelist.py +++ b/indoteknik_api/models/product_pricelist.py @@ -105,10 +105,12 @@ class ProductPricelist(models.Model): is_show_program = True else: is_show_program = False + + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') pricelist = self.search([ ('is_flash_sale', '=', True), - ('is_show_program', '=', is_show_program), + ('is_show_program', '=', True), ('start_date', '<=', current_time), ('end_date', '>=', current_time) ], order='start_date asc') -- cgit v1.2.3 From 840bc3f8c04933a56a31c279ef6e220c277bd36d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 29 Apr 2025 14:54:41 +0700 Subject: push --- indoteknik_api/models/product_pricelist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_api/models/product_pricelist.py b/indoteknik_api/models/product_pricelist.py index 744faaee..3ba38940 100644 --- a/indoteknik_api/models/product_pricelist.py +++ b/indoteknik_api/models/product_pricelist.py @@ -106,11 +106,11 @@ class ProductPricelist(models.Model): else: is_show_program = False - + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') pricelist = self.search([ ('is_flash_sale', '=', True), - ('is_show_program', '=', True), + ('is_show_program', '=', is_show_program), ('start_date', '<=', current_time), ('end_date', '>=', current_time) ], order='start_date asc') -- cgit v1.2.3 From 9642f8efdd9b7a63b58788251959e5968db8d7ee Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 30 Apr 2025 08:18:07 +0700 Subject: (andri) delete comment code --- indoteknik_custom/models/sale_order.py | 1 - 1 file changed, 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index ab63509b..b0643874 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1457,7 +1457,6 @@ class SaleOrder(models.Model): def _requires_approval_margin_manager(self): return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader - # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 53d840b330769988ac9efd4c4c6fe4f21969d850 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Wed, 30 Apr 2025 08:24:04 +0700 Subject: (andri) Penyesuaian Margin Approval (Live 1 Mei) --- indoteknik_custom/models/sale_order.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 37f767ec..78f2fa38 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1431,10 +1431,11 @@ class SaleOrder(models.Model): return False def _requires_approval_margin_leader(self): - return self.total_percent_margin < 15 and not self.env.user.is_leader + return self.total_percent_margin <= 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager + return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader + # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 475173ec46981926aa90e99f796e38ef1d46b61e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 10:15:35 +0700 Subject: sequence pricelist and fix bug validation invoice date and date doc kirim --- indoteknik_api/models/product_pricelist.py | 3 +-- indoteknik_custom/models/product_pricelist.py | 1 + indoteknik_custom/models/stock_picking.py | 2 +- indoteknik_custom/views/product_pricelist.xml | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/indoteknik_api/models/product_pricelist.py b/indoteknik_api/models/product_pricelist.py index 3ba38940..e0debf38 100644 --- a/indoteknik_api/models/product_pricelist.py +++ b/indoteknik_api/models/product_pricelist.py @@ -106,14 +106,13 @@ class ProductPricelist(models.Model): else: is_show_program = False - current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') pricelist = self.search([ ('is_flash_sale', '=', True), ('is_show_program', '=', is_show_program), ('start_date', '<=', current_time), ('end_date', '>=', current_time) - ], order='start_date asc') + ], order='number asc') return pricelist def is_flash_sale_product(self, product_id: int): diff --git a/indoteknik_custom/models/product_pricelist.py b/indoteknik_custom/models/product_pricelist.py index c299ff2f..ea3ee6cf 100644 --- a/indoteknik_custom/models/product_pricelist.py +++ b/indoteknik_custom/models/product_pricelist.py @@ -17,6 +17,7 @@ class ProductPricelist(models.Model): ], string='Flash Sale Option') banner_top = fields.Binary(string='Banner Top') flashsale_tag = fields.Char(string='Flash Sale Tag') + number = fields.Integer(string='Sequence') def _check_end_date_and_update_solr(self): today = datetime.utcnow().date() diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d2b1b9f2..0b688fab 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1187,7 +1187,7 @@ class StockPicking(models.Model): if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name or picking.partner_id.id == 96868: continue - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)], limit=1) + invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id), ('state','not in',['draft','cancel'])], limit=1) if not invoice: continue diff --git a/indoteknik_custom/views/product_pricelist.xml b/indoteknik_custom/views/product_pricelist.xml index 6eff0153..3c2b8b8d 100644 --- a/indoteknik_custom/views/product_pricelist.xml +++ b/indoteknik_custom/views/product_pricelist.xml @@ -20,6 +20,7 @@ +
    -- cgit v1.2.3 From 7b5b79e03e08dff76981dd9734d20c52f90c0b36 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 11:49:18 +0700 Subject: cr mrp production sale order required --- indoteknik_custom/models/mrp_production.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index ebbd1c24..87d75faf 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -10,7 +10,7 @@ class MrpProduction(models.Model): check_bom_product_lines = fields.One2many('check.bom.product', 'production_id', string='Check Product', auto_join=True, copy=False) desc = fields.Text(string='Description') - sale_order = fields.Many2one('sale.order', string='Sale Order', required=True, copy=False) + sale_order = fields.Many2one('sale.order', string='Sale Order', 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') @@ -30,6 +30,8 @@ class MrpProduction(models.Model): for record in self: if len(record.check_bom_product_lines) < 1: raise UserError("Check Product Tidak Boleh Kosong") + if not record.sale_order: + raise UserError("Sale Order Tidak Boleh Kosong") if record.sale_order and record.state == 'confirmed': message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name) record.sale_order.message_post(body=message) @@ -327,6 +329,8 @@ class CheckBomProduct(models.Model): ) for move in moves_to_reset: move.quantity_done = 0.0 + + production.qty_producing = 0 # Also sync remaining products in case their totals changed self._sync_check_product_to_moves(production) -- cgit v1.2.3 From 6f93b7a7a47481f6f308295a49bce40505041411 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 14:55:49 +0700 Subject: (andri) add field area (kecamatan & kota) pada stock picking --- indoteknik_custom/models/stock_picking.py | 9 +++++++++ indoteknik_custom/views/stock_picking.xml | 1 + 2 files changed, 10 insertions(+) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0b688fab..ccb551b0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,6 +157,15 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) + area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) + + @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + def _compute_area_name(self): + for record in self: + district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' + city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + record.area_name = f"{district}, {city}".strip(', ') + # def write(self, vals): # if 'linked_manual_bu_out' in vals: # for record in self: diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index c916f2ef..0a6f4752 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -26,6 +26,7 @@ --> + -- cgit v1.2.3 From a73856faaa1999b0c5ec20c41b37fda8a8eed31a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 15:13:52 +0700 Subject: (andri) add area (kecamatan kota) pada stock picking --- indoteknik_custom/models/stock_picking.py | 8 ++++++++ indoteknik_custom/views/stock_picking.xml | 1 + 2 files changed, 9 insertions(+) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 64dc1499..75803ccb 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,6 +157,14 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) + area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) + @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + def _compute_area_name(self): + for record in self: + district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' + city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + record.area_name = f"{district}, {city}".strip(', ') + # def write(self, vals): # if 'linked_manual_bu_out' in vals: # for record in self: diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index c916f2ef..0a6f4752 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -26,6 +26,7 @@ --> + -- cgit v1.2.3 From ee062e41281b7a1f582ff299ba4c671a4bbbe24d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 15:28:43 +0700 Subject: (andri) undo penyesuaian margin pada branch ini (agar tidak konflik) --- indoteknik_custom/models/sale_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b0643874..ab63509b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1457,6 +1457,7 @@ class SaleOrder(models.Model): def _requires_approval_margin_manager(self): return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader + # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 6601e72946ebcbca6b73b20dd4f3f86f39f89265 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 15:29:04 +0700 Subject: (andri) undo penyesuaian margin pada branch ini (agar tidak konflik) --- indoteknik_custom/models/sale_order.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index ab63509b..e25b9570 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1453,11 +1453,10 @@ class SaleOrder(models.Model): # Rule: # ≤ 15% → Pimpinan, > 15% - <24% → Manager, == 24% → Langsung Confirm def _requires_approval_margin_leader(self): - return self.total_percent_margin <= 15 and not self.env.user.is_leader + return self.total_percent_margin < 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader - # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager + return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 4c4414b0a4b0a51acfe7324c4c556cd0aa57c3c6 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 15:35:02 +0700 Subject: push --- indoteknik_custom/models/mrp_production.py | 8 ++++++++ indoteknik_custom/models/stock_move.py | 22 +++++++++++----------- indoteknik_custom/views/mrp_production.xml | 4 ++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 87d75faf..8179fe56 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -13,6 +13,14 @@ class MrpProduction(models.Model): sale_order = fields.Many2one('sale.order', string='Sale Order', 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') + state_reserve = fields.Selection([ + ('waiting', 'Waiting For Fullfilment'), + ('ready', 'Ready to Ship'), + ('done', 'Done'), + ('cancel', 'Cancelled'), + ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") + date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False) + @api.constrains('check_bom_product_lines') def constrains_check_bom_product_lines(self): diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index e75c75f0..42a6307a 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -15,18 +15,18 @@ class StockMove(models.Model): barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') - @api.model_create_multi - def create(self, vals_list): - moves = super(StockMove, self).create(vals_list) + # @api.model_create_multi + # def create(self, vals_list): + # moves = super(StockMove, self).create(vals_list) - for move in moves: - if move.product_id and move.location_id.id == 58 and move.location_dest_id.id == 57 and move.picking_type_id.id == 75: - po_line = self.env['purchase.order.line'].search([ - ('product_id', '=', move.product_id.id), - ('order_id.name', '=', move.origin) - ], limit=1) - if po_line: - move.write({'purchase_line_id': po_line.id}) + # for move in moves: + # if move.product_id and move.location_id.id == 58 and move.location_dest_id.id == 57 and move.picking_type_id.id == 75: + # po_line = self.env['purchase.order.line'].search([ + # ('product_id', '=', move.product_id.id), + # ('order_id.name', '=', move.origin) + # ], limit=1) + # if po_line: + # move.write({'purchase_line_id': po_line.id}) return moves diff --git a/indoteknik_custom/views/mrp_production.xml b/indoteknik_custom/views/mrp_production.xml index 419737f9..3de52a08 100644 --- a/indoteknik_custom/views/mrp_production.xml +++ b/indoteknik_custom/views/mrp_production.xml @@ -49,6 +49,10 @@ + + + + -- cgit v1.2.3 From f7dbba32f167b988cdbfbc0d89664e5595460a9c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 15:37:46 +0700 Subject: fix bug --- indoteknik_custom/models/stock_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 42a6307a..3c765244 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -28,7 +28,7 @@ class StockMove(models.Model): # if po_line: # move.write({'purchase_line_id': po_line.id}) - return moves + # return moves @api.constrains('product_id') def constrains_product_to_fill_vendor(self): -- cgit v1.2.3 From de1f159b50d42942381c0c7ab28e600b091c22ca Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 17:22:17 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f71c07e0..c35c3047 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,12 +157,12 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) - area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) - @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + area_name = fields.Char(string="Area", compute="_compute_area_name") + # @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') def _compute_area_name(self): for record in self: - district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' - city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + district = record.real_shipping_id.kelurahan_id.name if record.real_shipping_id.kelurahan_id else '' + city = record.real_shipping_id.kota_id.name if record.real_shipping_id.kota_id else '' record.area_name = f"{district}, {city}".strip(', ') # def write(self, vals): -- cgit v1.2.3 From c4dbf79115f0a468be9ff6e63f5c0dd4a7cf1718 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 2 May 2025 08:55:20 +0700 Subject: (andri) add validasi tax pada SO harus seragam --- indoteknik_custom/models/sale_order.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index e25b9570..e5ec271e 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -248,6 +248,16 @@ 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)]") + def _validate_uniform_taxes(self): + for order in self: + tax_sets = set() + for line in order.order_line: + tax_ids = tuple(sorted(line.tax_id.ids)) + if tax_ids: + tax_sets.add(tax_ids) + if len(tax_sets) > 1: + raise ValidationError("Semua produk dalam Sales Order harus memiliki kombinasi pajak yang sama.") + @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') def _check_total_margin_excl_third_party(self): for rec in self: @@ -1036,6 +1046,7 @@ class SaleOrder(models.Model): self._validate_order() for order in self: + order._validate_uniform_taxes() order.order_line.validate_line() term_days = 0 @@ -1088,6 +1099,7 @@ class SaleOrder(models.Model): self._validate_order() for order in self: + order._validate_uniform_taxes() order.order_line.validate_line() order.check_data_real_delivery_address() order._validate_order() @@ -1319,6 +1331,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: + order._validate_uniform_taxes() order.check_duplicate_product() order.check_product_bom() order.check_credit_limit() -- cgit v1.2.3 From da5754b2be5996cdffbe0ba778ed3b1fa6cf7d73 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 09:55:38 +0700 Subject: redirect website odoo to website indoteknik --- indoteknik_custom/controllers/website.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/controllers/website.py b/indoteknik_custom/controllers/website.py index 2e3df519..120dddad 100644 --- a/indoteknik_custom/controllers/website.py +++ b/indoteknik_custom/controllers/website.py @@ -1,7 +1,12 @@ -from odoo.http import request, Controller +from odoo.http import request, Controller, route from odoo import http, _ class Website(Controller): + + @route(['/shop', '/shop/cart'], auth='public', website=True) + def shop(self, **kw): + return request.redirect('https://indoteknik.com/shop/promo', code=302) + @http.route('/content', auth='public') def content(self, **kw): url = kw.get('url', '') -- cgit v1.2.3 From de3efad6b229594019754fc10db7c33db54fdcf4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 2 May 2025 13:38:37 +0700 Subject: change default status default value --- indoteknik_custom/models/commision.py | 103 +++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 46 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 6d832b85..2f3789eb 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -13,8 +13,10 @@ class CustomerRebate(models.Model): _inherit = ['mail.thread'] partner_id = fields.Many2one('res.partner', string='Customer', required=True) - date_from = fields.Date(string='Date From', required=True, help="Pastikan tanggal 1 januari, jika tidak, code akan break") - date_to = fields.Date(string='Date To', required=True, help="Pastikan tanggal 31 desember, jika tidak, code akan break") + date_from = fields.Date(string='Date From', required=True, + help="Pastikan tanggal 1 januari, jika tidak, code akan break") + date_to = fields.Date(string='Date To', required=True, + help="Pastikan tanggal 31 desember, jika tidak, code akan break") description = fields.Char(string='Description') target_1st = fields.Float(string='Target/Quarter 1st') target_2nd = fields.Float(string='Target/Quarter 2nd') @@ -36,7 +38,7 @@ class CustomerRebate(models.Model): line.dpp_q2 = line._get_current_dpp_q2(line) line.dpp_q3 = line._get_current_dpp_q3(line) line.dpp_q4 = line._get_current_dpp_q4(line) - + def _compute_achievement(self): for line in self: line.status_q1 = line._check_achievement(line.target_1st, line.target_2nd, line.dpp_q1) @@ -53,18 +55,18 @@ class CustomerRebate(models.Model): else: status = 'not achieve' return status - + def _get_current_dpp_q1(self, line): sum_dpp = 0 brand = [10, 89, 122] where = [ - ('move_id.move_type', '=', 'out_invoice'), - ('move_id.state', '=', 'posted'), - ('move_id.is_customer_commision', '=', False), - ('move_id.partner_id.id', '=', line.partner_id.id), - ('move_id.invoice_date', '>=', line.date_from), - ('move_id.invoice_date', '<=', '2023-03-31'), - ('product_id.x_manufacture', 'in', brand), + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'posted'), + ('move_id.is_customer_commision', '=', False), + ('move_id.partner_id.id', '=', line.partner_id.id), + ('move_id.invoice_date', '>=', line.date_from), + ('move_id.invoice_date', '<=', '2023-03-31'), + ('product_id.x_manufacture', 'in', brand), ] invoice_lines = self.env['account.move.line'].search(where, order='id') for invoice_line in invoice_lines: @@ -75,13 +77,13 @@ class CustomerRebate(models.Model): sum_dpp = 0 brand = [10, 89, 122] where = [ - ('move_id.move_type', '=', 'out_invoice'), - ('move_id.state', '=', 'posted'), - ('move_id.is_customer_commision', '=', False), - ('move_id.partner_id.id', '=', line.partner_id.id), - ('move_id.invoice_date', '>=', '2023-04-01'), - ('move_id.invoice_date', '<=', '2023-06-30'), - ('product_id.x_manufacture', 'in', brand), + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'posted'), + ('move_id.is_customer_commision', '=', False), + ('move_id.partner_id.id', '=', line.partner_id.id), + ('move_id.invoice_date', '>=', '2023-04-01'), + ('move_id.invoice_date', '<=', '2023-06-30'), + ('product_id.x_manufacture', 'in', brand), ] invoices = self.env['account.move.line'].search(where, order='id') for invoice in invoices: @@ -92,13 +94,13 @@ class CustomerRebate(models.Model): sum_dpp = 0 brand = [10, 89, 122] where = [ - ('move_id.move_type', '=', 'out_invoice'), - ('move_id.state', '=', 'posted'), - ('move_id.is_customer_commision', '=', False), - ('move_id.partner_id.id', '=', line.partner_id.id), - ('move_id.invoice_date', '>=', '2023-07-01'), - ('move_id.invoice_date', '<=', '2023-09-30'), - ('product_id.x_manufacture', 'in', brand), + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'posted'), + ('move_id.is_customer_commision', '=', False), + ('move_id.partner_id.id', '=', line.partner_id.id), + ('move_id.invoice_date', '>=', '2023-07-01'), + ('move_id.invoice_date', '<=', '2023-09-30'), + ('product_id.x_manufacture', 'in', brand), ] invoices = self.env['account.move.line'].search(where, order='id') for invoice in invoices: @@ -109,19 +111,20 @@ class CustomerRebate(models.Model): sum_dpp = 0 brand = [10, 89, 122] where = [ - ('move_id.move_type', '=', 'out_invoice'), - ('move_id.state', '=', 'posted'), - ('move_id.is_customer_commision', '=', False), - ('move_id.partner_id.id', '=', line.partner_id.id), - ('move_id.invoice_date', '>=', '2023-10-01'), - ('move_id.invoice_date', '<=', line.date_to), - ('product_id.x_manufacture', 'in', brand), + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'posted'), + ('move_id.is_customer_commision', '=', False), + ('move_id.partner_id.id', '=', line.partner_id.id), + ('move_id.invoice_date', '>=', '2023-10-01'), + ('move_id.invoice_date', '<=', line.date_to), + ('product_id.x_manufacture', 'in', brand), ] invoices = self.env['account.move.line'].search(where, order='id') for invoice in invoices: sum_dpp += invoice.price_subtotal return sum_dpp + class RejectReasonCommision(models.TransientModel): _name = 'reject.reason.commision' _description = 'Wizard for Reject Reason Customer Commision' @@ -150,16 +153,19 @@ class CustomerCommision(models.Model): partner_ids = fields.Many2many('res.partner', String='Customer', required=True) description = fields.Char(string='Description') notification = fields.Char(string='Notification') - commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', auto_join=True) + commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', + auto_join=True) status = fields.Selection([ + ('draft', 'Draft'), ('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') + ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange', default='draft') last_status = fields.Selection([ + ('draft', 'Draft'), ('pengajuan1', 'Menunggu Approval Manager Sales'), ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), @@ -209,7 +215,7 @@ class CustomerCommision(models.Model): for record in self: res = '' - + try: if record.commision_amt > 0: tb.parse(int(record.commision_amt)) @@ -222,15 +228,16 @@ class CustomerCommision(models.Model): 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) - + 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? @@ -255,22 +262,22 @@ class CustomerCommision(models.Model): self.commision_percent = achieve_2nd else: self.commision_percent = 0 - + self._onchange_commision_amt() @api.constrains('commision_percent') def _onchange_commision_percent(self): if not self.env.context.get('_onchange_commision_percent', True): return - + if self.commision_amt == 0: self.commision_amt = self.commision_percent * self.total_dpp // 100 - + @api.constrains('commision_amt') def _onchange_commision_amt(self): if not self.env.context.get('_onchange_commision_amt', True): return - + if self.total_dpp > 0 and self.commision_percent == 0: self.commision_percent = (self.commision_amt / self.total_dpp) * 100 @@ -322,7 +329,7 @@ class CustomerCommision(models.Model): raise UserError('Harus di approved oleh yang bersangkutan') return - def action_reject(self):#add 2 step approval + def action_reject(self): # add 2 step approval return { 'type': 'ir.actions.act_window', 'name': _('Reject Reason'), @@ -353,7 +360,7 @@ class CustomerCommision(models.Model): def generate_customer_commision(self): if self.commision_lines: raise UserError('Line sudah ada, tidak bisa di generate ulang') - + if self.commision_type == 'fee': self._generate_customer_commision_fee() else: @@ -428,11 +435,13 @@ class CustomerCommision(models.Model): }]) return + class CustomerCommisionLine(models.Model): _name = 'customer.commision.line' _order = 'id' - customer_commision_id = fields.Many2one('customer.commision', string='Ref', required=True, ondelete='cascade', copy=False) + customer_commision_id = fields.Many2one('customer.commision', string='Ref', required=True, ondelete='cascade', + copy=False) invoice_id = fields.Many2one('account.move', string='Invoice') partner_id = fields.Many2one('res.partner', string='Customer') state = fields.Char(string='InvStatus') @@ -440,10 +449,12 @@ 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') + 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' is_customer_commision = fields.Boolean(string='Customer Commision Used') -- cgit v1.2.3 From bb8a5981bdfd8a8d60db7fe509a989c29b2a4e5e Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 2 May 2025 15:49:43 +0700 Subject: (andri) rev area list picking --- indoteknik_custom/models/stock_picking.py | 8 ++++---- indoteknik_custom/views/stock_picking.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ccb551b0..e178ad1c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,13 +157,13 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) - area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) + area_name = fields.Char(string="Area", compute="_compute_area_name") - @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: - district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' - city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + district = record.real_shipping_id.kecamatan_id or '' + city = record.real_shipping_id.kota_id or '' record.area_name = f"{district}, {city}".strip(', ') # def write(self, vals): diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 0a6f4752..b45debd0 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -26,7 +26,7 @@ --> - + -- cgit v1.2.3 From dcfe69ad989118ce2d8dc41a9af1ece6b5a57d76 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 16:38:48 +0700 Subject: image carousel product --- indoteknik_custom/models/product_template.py | 18 +++++++++++++++++- indoteknik_custom/models/solr/product_product.py | 3 ++- indoteknik_custom/models/solr/product_template.py | 3 ++- indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/product_template.xml | 15 +++++++++++++++ 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index e6a01a04..a09570f4 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -15,6 +15,14 @@ _logger = logging.getLogger(__name__) class ProductTemplate(models.Model): _inherit = "product.template" + + image_carousel_lines = fields.One2many( + comodel_name="image.carousel", + inverse_name="product_id", + string="Image Carousel", + auto_join=True, + copy=False + ) x_studio_field_tGhJR = fields.Many2many('x_product_tags', string="Product Tags") x_manufacture = fields.Many2one( comodel_name="x_manufactures", @@ -246,7 +254,7 @@ class ProductTemplate(models.Model): # product.default_code = 'ITV.'+str(product.id) # _logger.info('Updated Variant %s' % product.name) - @api.onchange('name','default_code','x_manufacture','product_rating','website_description','image_1920','weight','public_categ_ids') + @api.onchange('name','default_code','x_manufacture','product_rating','website_description','image_1920','weight','public_categ_ids','image_carousel_lines') def update_solr_flag(self): for tmpl in self: if tmpl.solr_flag == 1: @@ -734,3 +742,11 @@ class OutstandingMove(models.Model): 'partially_available' ) """ % self._table) + +class ImageCarousel(models.Model): + _name = 'image.carousel' + _description = 'Image Carousel' + _order = 'product_id, id' + + product_id = fields.Many2one('product.template', string='Product', required=True, ondelete='cascade', index=True, copy=False) + image = fields.Binary(string='Image') diff --git a/indoteknik_custom/models/solr/product_product.py b/indoteknik_custom/models/solr/product_product.py index 667511b2..be5db814 100644 --- a/indoteknik_custom/models/solr/product_product.py +++ b/indoteknik_custom/models/solr/product_product.py @@ -67,10 +67,11 @@ class ProductProduct(models.Model): 'product_id_i': variant.id, 'template_id_i': variant.product_tmpl_id.id, 'image_s': ir_attachment.api_image('product.template', 'image_512', variant.product_tmpl_id.id), + 'image_carousel_s': [ir_attachment.api_image('image.carousel', 'image', carousel.product_id.id) for carousel in variant.product_tmpl_id.image_carousel_lines], 'image_mobile_s': ir_attachment.api_image('product.template', 'image_256', variant.product_tmpl_id.id), 'stock_total_f': variant.qty_stock_vendor, 'weight_f': variant.weight, - 'manufacture_id_i': variant.product_tmpl_id.x_manufacture.id or 0, + 'manufacture_id_i': variant.product_tmpl_id.x_manufacture.id or 0, 'manufacture_name_s': variant.product_tmpl_id.x_manufacture.x_name or '', 'manufacture_name': variant.product_tmpl_id.x_manufacture.x_name or '', 'image_promotion_1_s': ir_attachment.api_image('x_manufactures', 'image_promotion_1', variant.product_tmpl_id.x_manufacture.id), diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 8afff6e3..1f0ce918 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -26,7 +26,7 @@ class ProductTemplate(models.Model): 'function_name': function_name }) - @api.constrains('name', 'default_code', 'weight', 'x_manufacture', 'public_categ_ids', 'search_rank', 'search_rank_weekly', 'image_1920', 'unpublished') + @api.constrains('name', 'default_code', 'weight', 'x_manufacture', 'public_categ_ids', 'search_rank', 'search_rank_weekly', 'image_1920', 'unpublished','image_carousel_lines') def _create_solr_queue_sync_product_template(self): self._create_solr_queue('_sync_product_template_to_solr') @@ -94,6 +94,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), + 'image_carousel_s': [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.product_id.id) for carousel in template.image_carousel_lines], 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 24b8dfff..7d7c98f4 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -179,3 +179,4 @@ access_cancel_reason_order,cancel.reason.order,model_cancel_reason_order,,1,1,1, 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_production_purchase_match,access.production.purchase.match,model_production_purchase_match,,1,1,1,1 +access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1 diff --git a/indoteknik_custom/views/product_template.xml b/indoteknik_custom/views/product_template.xml index 076a8082..8f9d1190 100755 --- a/indoteknik_custom/views/product_template.xml +++ b/indoteknik_custom/views/product_template.xml @@ -53,6 +53,21 @@ + + + + + + + + + + image.carousel.tree + image.carousel + + + + -- cgit v1.2.3 From a3150fba1ce5b57c57fcd6dae8b0b2c61308709b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 17:03:42 +0700 Subject: push --- indoteknik_custom/models/solr/product_product.py | 4 +++- indoteknik_custom/models/solr/product_template.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/solr/product_product.py b/indoteknik_custom/models/solr/product_product.py index be5db814..d8bc3973 100644 --- a/indoteknik_custom/models/solr/product_product.py +++ b/indoteknik_custom/models/solr/product_product.py @@ -57,6 +57,8 @@ class ProductProduct(models.Model): is_in_bu = True if variant.qty_free_bandengan > 0 else False document = solr_model.get_doc('variants', variant.id) + + carousel = [ir_attachment.api_image('image.carousel', 'image', carousel.product_id.id) for carousel in variant.product_tmpl_id.image_carousel_lines], document.update({ 'id': variant.id, @@ -67,7 +69,7 @@ class ProductProduct(models.Model): 'product_id_i': variant.id, 'template_id_i': variant.product_tmpl_id.id, 'image_s': ir_attachment.api_image('product.template', 'image_512', variant.product_tmpl_id.id), - 'image_carousel_s': [ir_attachment.api_image('image.carousel', 'image', carousel.product_id.id) for carousel in variant.product_tmpl_id.image_carousel_lines], + 'image_carousel_s': [ir_attachment.api_image('image.carousel', 'image', carousel.id) for carousel in variant.product_tmpl_id.image_carousel_lines], 'image_mobile_s': ir_attachment.api_image('product.template', 'image_256', variant.product_tmpl_id.id), 'stock_total_f': variant.qty_stock_vendor, 'weight_f': variant.weight, diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 1f0ce918..80a57d66 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -85,6 +85,8 @@ class ProductTemplate(models.Model): cleaned_desc = BeautifulSoup(template.website_description or '', "html.parser").get_text() website_description = template.website_description if cleaned_desc else '' + carousel = [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines] + document = solr_model.get_doc('product', template.id) document.update({ "id": template.id, @@ -94,7 +96,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - 'image_carousel_s': [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.product_id.id) for carousel in template.image_carousel_lines], + 'image_carousel_s': [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From a91ffe639f73b8feac0e933524e92ed3e4168e69 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 17:09:02 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a017a090..b253090a 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1201,7 +1201,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: + if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date): raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From c48bd5f7fba6e75fe73a170ac3fbefbf3ac051e5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 17:23:04 +0700 Subject: push --- indoteknik_custom/models/solr/product_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 80a57d66..997415dc 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -96,7 +96,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - 'image_carousel_s': [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], + "image_carousel_s": [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 42671e5f4afd1d9646569791704bf9d66f265b5e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 22:19:01 +0700 Subject: trying to fix image_carousel --- indoteknik_custom/models/solr/product_template.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 997415dc..d9e83401 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -96,7 +96,10 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousel_s": [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], + 'image_carousel_s': {'set': [ + self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) + for carousel in template.image_carousel_lines + ]}, 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 986cf2eef7d5aafbdf8d936db5431f5ee1d1e830 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 22:24:00 +0700 Subject: push --- indoteknik_custom/models/solr/product_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index d9e83401..a1fc93cd 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -96,7 +96,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - 'image_carousel_s': {'set': [ + 'image_carousels': {'set': [ self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines ]}, -- cgit v1.2.3 From 3e0c3172cf929ee815dfdeccbd39a7ef91b5a152 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 3 May 2025 08:52:33 +0700 Subject: push image_carousels solr --- indoteknik_custom/models/solr/product_template.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index a1fc93cd..6c94efec 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -96,10 +96,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - 'image_carousels': {'set': [ - self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) - for carousel in template.image_carousel_lines - ]}, + "image_carousels": [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 7f6671fba5d872cfd0d80efadf667e039b95b0c5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 3 May 2025 09:54:51 +0700 Subject: push area --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b253090a..1291737e 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -161,8 +161,8 @@ class StockPicking(models.Model): @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: - district = record.real_shipping_id.kecamatan_id or '' - city = record.real_shipping_id.kota_id or '' + district = record.real_shipping_id.kecamatan_id.name or '' + city = record.real_shipping_id.kota_id.name or '' record.area_name = f"{district}, {city}".strip(', ') -- cgit v1.2.3 From 65f6e6ae297b535884c9563cfe1291f6c17157c9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 3 May 2025 10:22:38 +0700 Subject: push --- indoteknik_custom/models/commision.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 2f3789eb..32e81b9a 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -198,7 +198,8 @@ class CustomerCommision(models.Model): 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) + sales_id = fields.Many2one('res.users', string="Sales", tracking=True, default=lambda self: self.env.user, + domain=lambda self: [('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman').id)]) date_approved_sales = fields.Datetime(string="Date Approved Sales", tracking=True) date_approved_marketing = fields.Datetime(string="Date Approved Marketing", tracking=True) -- cgit v1.2.3 From 55a29fb33823733e486a24e6d5747b58d531294a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:26:51 +0700 Subject: try solr image carousel --- indoteknik_custom/models/solr/product_template.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 6c94efec..4b7139bd 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -85,8 +85,7 @@ class ProductTemplate(models.Model): cleaned_desc = BeautifulSoup(template.website_description or '', "html.parser").get_text() website_description = template.website_description if cleaned_desc else '' - carousel = [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines] - + carousel_images = ', '.join([self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines]) document = solr_model.get_doc('product', template.id) document.update({ "id": template.id, @@ -96,7 +95,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousels": [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], + "image_carousels": carousel_images, 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 957246b5836f94f3663230545467407740f10736 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:46:05 +0700 Subject: push --- indoteknik_custom/models/solr/product_template.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 4b7139bd..20402c84 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -85,7 +85,12 @@ class ProductTemplate(models.Model): cleaned_desc = BeautifulSoup(template.website_description or '', "html.parser").get_text() website_description = template.website_description if cleaned_desc else '' - carousel_images = ', '.join([self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines]) + # carousel_images = ', '.join([self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines]) + carousel_images = [] + for carousel in template.image_carousel_lines: + image_url = self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) + if image_url: # Hanya tambahkan jika URL valid + carousel_images.append(image_url) document = solr_model.get_doc('product', template.id) document.update({ "id": template.id, @@ -95,7 +100,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousels": carousel_images, + "image_carousel_ss": carousel_images, 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From fd802e74a981f93e232aea18a00ebc6399547028 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:50:23 +0700 Subject: push --- indoteknik_custom/models/ir_attachment.py | 6 ++++-- indoteknik_custom/models/solr/product_template.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/ir_attachment.py b/indoteknik_custom/models/ir_attachment.py index 6417fa3f..1acd0848 100644 --- a/indoteknik_custom/models/ir_attachment.py +++ b/indoteknik_custom/models/ir_attachment.py @@ -20,9 +20,11 @@ class Attachment(models.Model): return True if attachment else False def api_image(self, model, field, id): - base_url = self.env['ir.config_parameter'].get_param('web.base.url') + if not id: + return None + base_url = self.env['ir.config_parameter'].get_param('web.base.url').rstrip('/') is_found = self.is_found(model, field, id) - return base_url + 'api/image/' + model + '/' + field + '/' + str(id) if is_found else '' + return f"{base_url}/api/image/{model}/{field}/{id}" if is_found else None def api_image_local(self, model, field, id): base_url = self.env['ir.config_parameter'].get_param('web.base.local_url') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 20402c84..42cab882 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -100,7 +100,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousel_ss": carousel_images, + "image_carousels_s": ' || '.join(carousel_images) # Gunakan pemisah yang unik 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 8d4c3a9b7673a17dddec8ece8ea7f75961c8ed54 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:51:34 +0700 Subject: push --- indoteknik_custom/models/solr/product_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 42cab882..4220f2f5 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -100,7 +100,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousels_s": ' || '.join(carousel_images) # Gunakan pemisah yang unik + "image_carousels_s": ' || '.join(carousel_images), # Gunakan pemisah yang unik 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 86cccc64c6f67b031a1bc345aae7922d83e021ea Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:58:48 +0700 Subject: push --- indoteknik_custom/models/ir_attachment.py | 6 ++---- indoteknik_custom/models/solr/product_template.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/ir_attachment.py b/indoteknik_custom/models/ir_attachment.py index 1acd0848..6417fa3f 100644 --- a/indoteknik_custom/models/ir_attachment.py +++ b/indoteknik_custom/models/ir_attachment.py @@ -20,11 +20,9 @@ class Attachment(models.Model): return True if attachment else False def api_image(self, model, field, id): - if not id: - return None - base_url = self.env['ir.config_parameter'].get_param('web.base.url').rstrip('/') + base_url = self.env['ir.config_parameter'].get_param('web.base.url') is_found = self.is_found(model, field, id) - return f"{base_url}/api/image/{model}/{field}/{id}" if is_found else None + return base_url + 'api/image/' + model + '/' + field + '/' + str(id) if is_found else '' def api_image_local(self, model, field, id): base_url = self.env['ir.config_parameter'].get_param('web.base.local_url') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 4220f2f5..20402c84 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -100,7 +100,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousels_s": ' || '.join(carousel_images), # Gunakan pemisah yang unik + "image_carousel_ss": carousel_images, 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 982e2b966385965328aafaa8607ca31d811874d9 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 5 May 2025 10:43:10 +0700 Subject: date reserved bu/out = validate date bu/pick --- indoteknik_custom/models/stock_picking.py | 505 +++++++++++++++++------------- 1 file changed, 279 insertions(+), 226 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1291737e..ce1399fe 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -15,22 +15,26 @@ import requests import time import logging import re + _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" _biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" + + # _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" - - + 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, copy=False) + konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True, + copy=False) scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True, copy=False) check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True, copy=False) - - check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, copy=False) + + check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, + copy=False) barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') account_id = fields.Many2one('account.account', string='Account') @@ -89,18 +93,23 @@ class StockPicking(models.Model): approval_status = fields.Selection([ ('pengajuan1', 'Approval Accounting'), ('approved', 'Approved'), - ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Internal Use") + ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3, + help="Approval Status untuk Internal Use") approval_receipt_status = fields.Selection([ ('pengajuan1', 'Approval Logistic'), ('approved', 'Approved'), - ], string='Approval Receipt Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Receipt") + ], string='Approval Receipt Status', readonly=True, copy=False, index=True, tracking=3, + help="Approval Status untuk Receipt") approval_return_status = fields.Selection([ ('pengajuan1', 'Approval Finance'), ('approved', 'Approved'), - ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") - date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, copy=False) + ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, + help="Approval Status untuk Return") + date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', + help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, + copy=False) note_logistic = fields.Selection([ ('wait_so_together', 'Tunggu SO Barengan'), ('not_paid', 'Customer belum bayar'), @@ -110,7 +119,8 @@ class StockPicking(models.Model): ('expedition_closed', 'Eskpedisi belum buka') ], string='Note Logistic', help='jika field ini diisi maka tidak akan dihitung ke lead time') waybill_id = fields.One2many(comodel_name='airway.bill', inverse_name='do_id', string='Airway Bill') - purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', string="Purchase Representative") + purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', + string="Purchase Representative") carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method') shipping_status = fields.Char(string='Shipping Status', compute="_compute_shipping_status") date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False) @@ -131,9 +141,9 @@ class StockPicking(models.Model): ('invoiced', 'Fully Invoiced'), ('to invoice', 'To Invoice'), ('no', 'Nothing to Invoice') - ], string='Invoice Status', related="sale_id.invoice_status") + ], string='Invoice Status', related="sale_id.invoice_status") note_return = fields.Text(string="Note Return", help="Catatan untuk kirim barang kembali") - + state_reserve = fields.Selection([ ('waiting', 'Waiting For Fullfilment'), ('ready', 'Ready to Ship'), @@ -146,7 +156,8 @@ class StockPicking(models.Model): ('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.") + ], 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): @@ -158,13 +169,13 @@ class StockPicking(models.Model): linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) area_name = fields.Char(string="Area", compute="_compute_area_name") + @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: district = record.real_shipping_id.kecamatan_id.name or '' city = record.real_shipping_id.kota_id.name or '' record.area_name = f"{district}, {city}".strip(', ') - # def write(self, vals): # if 'linked_manual_bu_out' in vals: @@ -189,7 +200,7 @@ class StockPicking(models.Model): # picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) # picking.state_packing = 'packing_done' # return record - + @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: @@ -209,8 +220,10 @@ class StockPicking(models.Model): for picking in self: picking.dokumen_pengiriman = picking.partner_id.dokumen_pengiriman_input - dokumen_tanda_terima = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_tanda_terima) - dokumen_pengiriman = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_pengiriman) + dokumen_tanda_terima = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang', + readonly=True, compute=_compute_dokumen_tanda_terima) + dokumen_pengiriman = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang', readonly=True, + compute=_compute_dokumen_pengiriman) # Envio Tracking Section envio_id = fields.Char(string="Envio ID", readonly=True) @@ -255,8 +268,10 @@ class StockPicking(models.Model): # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) 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') + 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') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') @@ -266,10 +281,10 @@ class StockPicking(models.Model): if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'): kirim_date = fields.Datetime.from_string(record.last_update_date_doc_kirim) now = fields.Datetime.now() - + deadline = kirim_date + timedelta(days=1) deadline = deadline.replace(hour=10, minute=0, second=0) - + if now > deadline: raise ValidationError( _("Anda tidak dapat mengubah Tanggal Kirim setelah jam 10:00 pada hari berikutnya!") @@ -281,12 +296,15 @@ class StockPicking(models.Model): rec.calculate_line_no() if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name and rec.partner_id.id != 96868: - invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') + invoice = self.env['account.move'].search( + [('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], + limit=1, order='create_date desc') if invoice and not self.env.context.get('active_model') == 'stock.picking': rec._check_date_doc_kirim_modification() if rec.date_doc_kirim != invoice.invoice_date and not self.env.context.get('from_button_approve'): - get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) + get_approval_invoice_date = self.env['approval.invoice.date'].search( + [('picking_id', '=', rec.id), ('state', '=', 'draft')], limit=1) if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': get_approval_invoice_date.date_doc_do = rec.date_doc_kirim @@ -306,12 +324,13 @@ class StockPicking(models.Model): return { 'type': 'ir.actions.client', 'tag': 'display_notification', - 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, + 'params': {'title': 'Notification', + 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', + 'next': {'type': 'ir.actions.act_window_close'}}, } - + rec.last_update_date_doc_kirim = datetime.datetime.utcnow() - - + @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): now = datetime.datetime.utcnow() @@ -319,16 +338,18 @@ class StockPicking(models.Model): if len(picking.scan_koli_lines) > 0: if len(picking.scan_koli_lines) != picking.total_mapping_koli: raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli") - + 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')]) + 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')]) + 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): @@ -356,21 +377,21 @@ class StockPicking(models.Model): 'koli_id': rec.id, }) else: - raise UserError('Tidak Bisa Mengubah Quantity Koli Karena Koli Dari Picking Ini Sudah Dipakai Di BU/OUT!') + 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)}", + '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();" self.env.cr.execute(query) - - + # @api.depends('estimated_ready_ship_date', 'state') # def _callculate_sequance(self): # for record in self: @@ -379,9 +400,9 @@ class StockPicking(models.Model): # rts = record.estimated_ready_ship_date - waktu.now() # rts_days = rts.days # rts_hours = divmod(rts.seconds, 3600) - + # estimated_by_erts = rts.total_seconds() / 3600 - + # record.countdown_ready_to_ship = f"{rts_days} days, {rts_hours} hours" # record.countdown_hours = estimated_by_erts # else: @@ -391,7 +412,6 @@ class StockPicking(models.Model): # _logger.error(f"Error calculating sequance {record.id}: {str(e)}") # print(str(e)) # return { 'error': str(e) } - # @api.depends('estimated_ready_ship_date', 'state') # def _compute_countdown_hours(self): @@ -431,7 +451,7 @@ class StockPicking(models.Model): ('state', '=', 'done'), ('carrier_id', '=', 9), ('lalamove_order_id', '!=', False) - ]) + ]) for picking in pickings: try: order_id = picking.lalamove_order_id @@ -466,7 +486,7 @@ class StockPicking(models.Model): for stop in stops: pod = stop.get("POD", {}) if pod.get("status") == "DELIVERED": - image_url = pod.get("image") # Sesuaikan jika key berbeda + image_url = pod.get("image") # Sesuaikan jika key berbeda self.lalamove_image_url = image_url address = stop.get("address") @@ -487,7 +507,6 @@ class StockPicking(models.Model): else: raise UserError(f"Error {response.status_code}: {response.text}") - def _convert_to_wib(self, date_str): """ Mengonversi string waktu ISO 8601 ke format waktu Indonesia (WIB) @@ -573,7 +592,7 @@ class StockPicking(models.Model): picking.tracking_by = self.env.user.id ata_at_str = data.get("ata_at") envio_ata = self._convert_to_datetime(data.get("ata_at")) - + picking.driver_arrival_date = envio_ata if data.get("status") != 'delivered': picking.driver_arrival_date = False @@ -584,13 +603,13 @@ class StockPicking(models.Model): raise UserError(f"Kesalahan tidak terduga: {str(e)}") def action_send_to_biteship(self): - + if self.biteship_tracking_id: raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}") - + # Mencari data sale.order.line berdasarkan sale_id products = self.env['sale.order.line'].search([('order_id', '=', self.sale_id.id)]) - + # Fungsi untuk membangun items_data dari order lines def build_items_data(lines): return [{ @@ -612,7 +631,7 @@ class StockPicking(models.Model): ('order_id', '=', self.sale_id.id), ('product_id', '=', move_line.product_id.id) ], limit=1) - + if order_line: items_data_instant.append({ "name": order_line.product_id.name, @@ -623,7 +642,7 @@ class StockPicking(models.Model): }) payload = { - "reference_id " : self.sale_id.name, + "reference_id ": self.sale_id.name, "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', "shipper_organization": self.carrier_id.name, @@ -644,19 +663,20 @@ class StockPicking(models.Model): } # Cek jika pengiriman instant atau same_day - if self.sale_id.delivery_service_type and ("instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): + if self.sale_id.delivery_service_type and ( + "instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): payload.update({ - "origin_coordinate" :{ + "origin_coordinate": { "latitude": -6.3031123, - "longitude" : 106.7794934999 + "longitude": 106.7794934999 }, - "destination_coordinate" : { + "destination_coordinate": { "latitude": self.real_shipping_id.latitude, "longitude": self.real_shipping_id.longtitude, }, "items": items_data_instant }) - + api_key = _biteship_api_key headers = { "Authorization": f"Bearer {api_key}", @@ -664,7 +684,7 @@ class StockPicking(models.Model): } # Kirim request ke Biteship - response = requests.post(_biteship_url+'/orders', headers=headers, json=payload) + response = requests.post(_biteship_url + '/orders', headers=headers, json=payload) if response.status_code == 200: data = response.json() @@ -673,7 +693,7 @@ class StockPicking(models.Model): self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "") self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "") self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "") - + waybill_id = data.get("courier", {}).get("waybill_id", "") message = f"✅ Berhasil Order ke Biteship! Resi: {waybill_id}" if waybill_id else "⚠️ Order berhasil, tetapi tidak ada nomor resi." @@ -690,10 +710,10 @@ class StockPicking(models.Model): error_message = error_data.get("error", "Unknown error") error_code = error_data.get("code", "No code provided") raise UserError(f"Error saat mengirim ke Biteship: {error_message} (Code: {error_code})") - + @api.constrains('driver_departure_date') - def constrains_driver_departure_date(self): - if not self.date_doc_kirim: + def constrains_driver_departure_date(self): + if not self.date_doc_kirim: self.date_doc_kirim = self.driver_departure_date @api.constrains('arrival_time') @@ -721,32 +741,32 @@ class StockPicking(models.Model): if not self._context.get('darimana') == 'sale.order' and self.env.user.id not in users_in_group.mapped('id'): self.sale_id.unreserve_id = self.id return self._create_approval_notification('Logistic') - + res = super(StockPicking, self).do_unreserve() current_time = datetime.datetime.utcnow() self.date_unreserve = current_time - + return res - + def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'internal'), ('name', 'ilike', 'BU/PICK/'), ]) - + for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ ('sale_order_id', '=', picking.sale_id.id) ]) - + picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' - + self.check_state_reserve_backorder() def check_state_reserve_backorder(self): @@ -756,29 +776,29 @@ class StockPicking(models.Model): ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) - + for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ ('sale_order_id', '=', picking.sale_id.id) ]) - + picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' - + def _create_approval_notification(self, approval_role): title = 'Warning' message = f'Butuh approval sales untuk unreserved' return self._create_notification_action(title, message) - + def _create_notification_action(self, title, message): return { 'type': 'ir.actions.client', 'tag': 'display_notification', - 'params': { 'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'} }, + 'params': {'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'}}, } def _compute_shipping_status(self): @@ -788,7 +808,7 @@ class StockPicking(models.Model): status = 'shipment' elif rec.driver_departure_date and (rec.sj_return_date or rec.driver_arrival_date): status = 'completed' - + rec.shipping_status = status def action_create_invoice_from_mr(self): @@ -796,10 +816,10 @@ class StockPicking(models.Model): """ if not self.env.user.is_accounting: raise UserError('Hanya Accounting yang bisa membuat Bill') - + precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') - #custom here + # custom here po = self.env['purchase.order'].search([ ('name', '=', self.group_id.name) ]) @@ -816,24 +836,29 @@ class StockPicking(models.Model): invoice_vals = order._prepare_invoice() # Invoice line values (keep only necessary sections). for line in self.move_ids_without_package: - po_line = self.env['purchase.order.line'].search([('order_id', '=', po.id), ('product_id', '=', line.product_id.id)], limit=1) + po_line = self.env['purchase.order.line'].search( + [('order_id', '=', po.id), ('product_id', '=', line.product_id.id)], limit=1) qty = line.product_uom_qty if po_line.display_type == 'line_section': pending_section = line continue if not float_is_zero(po_line.qty_to_invoice, precision_digits=precision): if pending_section: - invoice_vals['invoice_line_ids'].append((0, 0, pending_section._prepare_account_move_line_from_mr(po_line, qty))) + invoice_vals['invoice_line_ids'].append( + (0, 0, pending_section._prepare_account_move_line_from_mr(po_line, qty))) pending_section = None - invoice_vals['invoice_line_ids'].append((0, 0, line._prepare_account_move_line_from_mr(po_line, qty))) + invoice_vals['invoice_line_ids'].append( + (0, 0, line._prepare_account_move_line_from_mr(po_line, qty))) invoice_vals_list.append(invoice_vals) if not invoice_vals_list: - raise UserError(_('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.')) + raise UserError( + _('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.')) # 2) group by (company_id, partner_id, currency_id) for batch creation new_invoice_vals_list = [] - for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: (x.get('company_id'), x.get('partner_id'), x.get('currency_id'))): + for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: ( + x.get('company_id'), x.get('partner_id'), x.get('currency_id'))): origins = set() payment_refs = set() refs = set() @@ -863,7 +888,8 @@ class StockPicking(models.Model): # 4) Some moves might actually be refunds: convert them if the total amount is negative # We do this after the moves have been created since we need taxes, etc. to know if the total # is actually negative or not - moves.filtered(lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note() + moves.filtered( + lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note() return self.action_view_invoice_from_mr(moves) @@ -926,7 +952,7 @@ class StockPicking(models.Model): # for stock_move_line in stock_move_lines: # if stock_move_line.picking_id.state not in list_state: # continue - # raise UserError('Sudah pernah dikirim kalender') + # raise UserError('Sudah pernah dikirim kalender') for pick in self: if not pick.is_internal_use: @@ -952,18 +978,20 @@ class StockPicking(models.Model): if self.picking_type_code == 'outgoing': if self.env.user.id in [3988, 3401, 20] or ( - self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin + self.env.user.has_group( + 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin ): action['context'] = {'picking_ids': [x.id for x in self]} return action - elif not self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin: + elif not self.env.user.has_group( + 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin: raise UserError('Harus Purchasing yang Ask Return') else: raise UserError('Harus Sales Admin yang Ask Return') elif self.picking_type_code == 'incoming': if self.env.user.has_group('indoteknik_custom.group_role_purchasing') or ( - self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin + self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin ): action['context'] = {'picking_ids': [x.id for x in self]} return action @@ -973,7 +1001,7 @@ class StockPicking(models.Model): raise UserError('Harus Purchasing yang Ask Return') def calculate_line_no(self): - + for picking in self: name = picking.group_id.name for move in picking.move_ids_without_package: @@ -996,10 +1024,10 @@ class StockPicking(models.Model): def _compute_summary_qty(self): for picking in self: sum_qty_detail = sum_qty_operation = count_line_detail = count_line_operation = 0 - for detail in picking.move_line_ids_without_package: # detailed operations + for detail in picking.move_line_ids_without_package: # detailed operations sum_qty_detail += detail.qty_done count_line_detail += 1 - for operation in picking.move_ids_without_package: # operations + for operation in picking.move_ids_without_package: # operations sum_qty_operation += operation.product_uom_qty count_line_operation += 1 picking.summary_qty_detail = sum_qty_detail @@ -1021,13 +1049,13 @@ class StockPicking(models.Model): ]) if ( - self.picking_type_id.id == 29 - and quant - and line.location_id.id == bu_location_id - and quant.inventory_quantity < line.product_uom_qty + self.picking_type_id.id == 29 + and quant + and line.location_id.id == bu_location_id + and quant.inventory_quantity < line.product_uom_qty ): raise UserError('Quantity reserved lebih besar dari quantity onhand di product') - + def check_qty_done_stock(self): for line in self.move_line_ids_without_package: def check_qty_per_inventory(self, product, location): @@ -1040,10 +1068,11 @@ class StockPicking(models.Model): return quant.quantity return 0 - + 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])]) @@ -1068,7 +1097,8 @@ class StockPicking(models.Model): 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': + 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") @@ -1076,37 +1106,36 @@ 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' - - 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): + 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): + 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 not self.linked_manual_bu_out and 'BU/PICK/' in self.name: raise UserError(_("Isi BU Out terlebih dahulu!")) - + 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)) - + 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") @@ -1128,7 +1157,7 @@ class StockPicking(models.Model): if self.is_internal_use and not self.env.user.is_accounting: raise UserError("Harus di Approve oleh Accounting") - + if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: raise UserError("Harus di Approve oleh Logistik") @@ -1163,6 +1192,16 @@ class StockPicking(models.Model): self.final_seq = 0 self.set_picking_code_out() self.send_koli_to_so() + + if (self.state_reserve == 'done' and self.picking_type_code == 'internal' and 'BU/PICK/' in self.name + and self.linked_manual_bu_out): + if not self.linked_manual_bu_out.date_reserved: + current_datetime = datetime.datetime.utcnow() + self.linked_manual_bu_out.date_reserved = current_datetime + self.linked_manual_bu_out.message_post( + body=f"Date Reserved diisi secara otomatis dari validasi BU/PICK {self.name}" + ) + if not self.env.context.get('skip_koli_check'): for picking in self: if picking.sale_id: @@ -1172,7 +1211,8 @@ class StockPicking(models.Model): 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_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 @@ -1190,36 +1230,37 @@ class StockPicking(models.Model): } self.send_mail_bills() return res - + def check_invoice_date(self): for picking in self: if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name or picking.partner_id.id == 96868: continue - - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id), ('state','not in',['draft','cancel'])], limit=1) - + + invoice = self.env['account.move'].search( + [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel'])], limit=1) + if not invoice: continue - + if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date): raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") - + picking_date = fields.Date.to_date(picking.date_doc_kirim) invoice_date = fields.Date.to_date(invoice.invoice_date) - + if picking_date != invoice_date and picking.update_date_doc_kirim_add: raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % ( picking_date.strftime('%d-%m-%Y'), invoice_date.strftime('%d-%m-%Y') )) - + 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/%'), @@ -1228,11 +1269,10 @@ class StockPicking(models.Model): ('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: @@ -1240,7 +1280,7 @@ class StockPicking(models.Model): 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: @@ -1257,7 +1297,7 @@ class StockPicking(models.Model): '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: @@ -1265,7 +1305,7 @@ class StockPicking(models.Model): ('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): @@ -1280,7 +1320,7 @@ class StockPicking(models.Model): return quant.quantity return 0 - + 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') @@ -1337,11 +1377,14 @@ 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' or self.env.context.get('active_model') == 'stock.picking.type'): + if not self.env.user.is_logistic_approver and ( + self.env.context.get('active_model') == 'stock.picking' or self.env.context.get( + 'active_model') == 'stock.picking.type'): 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') and self.picking_type_code == 'outgoing': + + if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group( + 'indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing': raise UserError("Button ini hanya untuk Logistik") res = super(StockPicking, self).action_cancel() @@ -1351,7 +1394,7 @@ class StockPicking(models.Model): def create(self, vals): self._use_faktur(vals) records = super(StockPicking, self).create(vals) - + # Panggil sync_sale_line setelah record dibuat # records.sync_sale_line(vals) return records @@ -1373,8 +1416,8 @@ class StockPicking(models.Model): def write(self, vals): if 'linked_manual_bu_out' in vals: for record in self: - if (record.picking_type_code == 'internal' - and 'BU/PICK/' in record.name): + if (record.picking_type_code == 'internal' + and 'BU/PICK/' in record.name): # Jika menghapus referensi (nilai di-set False/None) if record.linked_manual_bu_out and not vals['linked_manual_bu_out']: record.linked_manual_bu_out.state_packing = 'not_packing' @@ -1399,7 +1442,8 @@ class StockPicking(models.Model): if name_to_modify.startswith('BU/INT'): new_name = name_to_modify.replace('BU/INT', 'BU/IN', 1) # Periksa apakah nama sudah ada - if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0: + if self.env['stock.picking'].search_count( + [('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0: new_name = f"{new_name}-DUP" vals['name'] = new_name return super(StockPicking, self).write(vals) @@ -1443,7 +1487,7 @@ class StockPicking(models.Model): def get_manifests(self): if self.waybill_id and len(self.waybill_id.manifest_ids) > 0: return [self.create_manifest_data(x.description, x.datetime) for x in self.waybill_id.manifest_ids] - + status_mapping = { 'pickup': { 'arrival': 'Sudah diambil', @@ -1468,7 +1512,7 @@ class StockPicking(models.Model): if not status: return manifest_datas - + if arrival_date or self.sj_return_date: manifest_datas.append(self.create_manifest_data(status['arrival'], arrival_date)) if departure_date: @@ -1479,14 +1523,14 @@ class StockPicking(models.Model): def get_tracking_detail(self): self.ensure_one() - + order = self.env['sale.order'].search([('name', '=', self.sale_id.name)], limit=1) response = { 'delivery_order': { 'name': self.name, 'carrier': self.carrier_id.name or '', - 'service' : order.delivery_service_type or '', + 'service': order.delivery_service_type or '', 'receiver_name': '', 'receiver_city': '' }, @@ -1498,74 +1542,76 @@ class StockPicking(models.Model): 'is_biteship': True if self.biteship_id else False, 'manifests': self.get_manifests() } - - if self.biteship_id : + + if self.biteship_id: histori = self.get_manifest_biteship() eta_start = order.date_order + timedelta(days=order.estimated_arrival_days_start) eta_end = order.date_order + timedelta(days=order.estimated_arrival_days) formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}" response['eta'] = formatted_eta - response['manifests'] = histori.get("manifests", []) - response['delivered'] = histori.get("delivered", False) or self.sj_return_date != False or self.driver_arrival_date != False + response['manifests'] = histori.get("manifests", []) + response['delivered'] = histori.get("delivered", + False) or self.sj_return_date != False or self.driver_arrival_date != False response['status'] = self._map_status_biteship(histori.get("delivered")) - + return response if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0: response['delivered'] = self.sj_return_date != False or self.driver_arrival_date != False return response - + response['delivery_order']['receiver_name'] = self.waybill_id.receiver_name response['delivery_order']['receiver_city'] = self.waybill_id.receiver_city response['delivery_status'] = self.waybill_id._get_history('delivery_status') response['delivered'] = self.waybill_id.delivered return response - + def get_manifest_biteship(self): api_key = _biteship_api_key headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } - - + manifests = [] - + try: - # Kirim request ke Biteship - response = requests.get(_biteship_url+'/trackings/'+self.biteship_tracking_id, headers=headers, json=manifests) + # Kirim request ke Biteship + response = requests.get(_biteship_url + '/trackings/' + self.biteship_tracking_id, headers=headers, + json=manifests) result = response.json() description = { - 'confirmed' : 'Indoteknik telah melakukan permintaan pick-up', - 'allocated' : 'Kurir akan melakukan pick-up pesanan', - 'picking_up' : 'Kurir sedang dalam perjalanan menuju lokasi pick-up', - 'picked' : 'Pesanan sudah di pick-up kurir '+result.get("courier", {}).get("name", ""), - 'on_hold' : 'Pesanan ditahan sementara karena masalah pengiriman', - 'dropping_off' : 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', - 'delivered' : 'Pesanan telah sampai dan diterima oleh '+result.get("destination", {}).get("contact_name", "") + 'confirmed': 'Indoteknik telah melakukan permintaan pick-up', + 'allocated': 'Kurir akan melakukan pick-up pesanan', + 'picking_up': 'Kurir sedang dalam perjalanan menuju lokasi pick-up', + 'picked': 'Pesanan sudah di pick-up kurir ' + result.get("courier", {}).get("name", ""), + 'on_hold': 'Pesanan ditahan sementara karena masalah pengiriman', + 'dropping_off': 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', + 'delivered': 'Pesanan telah sampai dan diterima oleh ' + result.get("destination", {}).get( + "contact_name", "") } - if(result.get('success') == True): + if (result.get('success') == True): history = result.get("history", []) status = result.get("status", "") - + for entry in reversed(history): manifests.append({ "status": re.sub(r'[^a-zA-Z0-9\s]', ' ', entry["status"]).lower().capitalize(), "datetime": self._convert_to_local_time(entry["updated_at"]), - "description": description[entry["status"]], + "description": description[entry["status"]], }) - + return { "manifests": manifests, "delivered": status } return manifests - except Exception as e : + except Exception as e: _logger.error(f"Error fetching Biteship order for picking {self.id}: {str(e)}") - return { 'error': str(e) } - + return {'error': str(e)} + def _convert_to_local_time(self, iso_date): try: dt_with_tz = waktu.fromisoformat(iso_date) @@ -1577,7 +1623,7 @@ class StockPicking(models.Model): return local_dt.strftime("%Y-%m-%d %H:%M:%S") except Exception as e: return str(e) - + def _map_status_biteship(self, status): status_mapping = { "confirmed": "pending", @@ -1591,7 +1637,7 @@ class StockPicking(models.Model): "delivered": "completed" } return status_mapping.get(status, "Hubungi Admin") - + def generate_eta_delivery(self): current_date = datetime.datetime.now() prepare_days = 3 @@ -1605,7 +1651,7 @@ class StockPicking(models.Model): fastest_eta = start_date + ead_datetime if not self.driver_departure_date and fastest_eta < current_date: fastest_eta = current_date + ead_datetime - + longest_days = 3 longest_eta = fastest_eta + datetime.timedelta(days=longest_days) @@ -1614,9 +1660,10 @@ class StockPicking(models.Model): formatted_fastest_eta = fastest_eta.strftime(format_time_fastest) formatted_longest_eta = longest_eta.strftime(format_time) - + return f'{formatted_fastest_eta} - {formatted_longest_eta}' - + + class CheckProduct(models.Model): _name = 'check.product' _description = 'Check Product' @@ -1639,7 +1686,7 @@ class CheckProduct(models.Model): def _onchange_code_product(self): if not self.code_product: return - + # Cari product berdasarkan default_code, barcode, atau barcode_box product = self.env['product.product'].search([ '|', @@ -1648,10 +1695,10 @@ class CheckProduct(models.Model): ('barcode', '=', self.code_product), ('barcode_box', '=', self.code_product) ], limit=1) - + if not product: raise UserError("Product tidak ditemukan") - + # Jika scan barcode_box, set quantity sesuai qty_pcs_box if product.barcode_box == self.code_product: self.product_id = product.id @@ -1660,7 +1707,7 @@ class CheckProduct(models.Model): # return { # 'warning': { # 'title': 'Info',8994175025871 - + # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}' # } # } @@ -1673,29 +1720,29 @@ class CheckProduct(models.Model): def unlink(self): # Get all affected pickings before deletion pickings = self.mapped('picking_id') - + # Store product_ids that will be deleted deleted_product_ids = self.mapped('product_id') - + # Perform the deletion result = super(CheckProduct, self).unlink() - + # After deletion, update moves for affected pickings for picking in pickings: # For products that were completely removed (no remaining check.product lines) remaining_product_ids = picking.check_product_lines.mapped('product_id') removed_product_ids = deleted_product_ids - remaining_product_ids - + # Set quantity_done to 0 for moves of completely removed products moves_to_reset = picking.move_ids_without_package.filtered( lambda move: move.product_id in removed_product_ids ) for move in moves_to_reset: move.quantity_done = 0.0 - + # Also sync remaining products in case their totals changed self._sync_check_product_to_moves(picking) - + return result @api.depends('quantity') @@ -1711,7 +1758,6 @@ class CheckProduct(models.Model): else: record.status = 'Done' - def create(self, vals): # Create the record record = super(CheckProduct, self).create(vals) @@ -1736,7 +1782,8 @@ class CheckProduct(models.Model): for product_id in picking.check_product_lines.mapped('product_id'): # Totalkan quantity dari semua baris check.product untuk product_id ini total_quantity = sum( - line.quantity for line in picking.check_product_lines.filtered(lambda line: line.product_id == product_id) + line.quantity for line in + picking.check_product_lines.filtered(lambda line: line.product_id == product_id) ) # Update quantity_done di move yang relevan moves = picking.move_ids_without_package.filtered(lambda move: move.product_id == product_id) @@ -1789,8 +1836,8 @@ class CheckProduct(models.Model): if not moves: raise UserError(( - "The product '%s' tidak ada di operations. " - ) % record.product_id.display_name) + "The product '%s' tidak ada di operations. " + ) % record.product_id.display_name) total_qty_in_moves = sum(moves.mapped('product_uom_qty')) @@ -1808,18 +1855,19 @@ class CheckProduct(models.Model): if total_quantity > total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand." - ) % (record.product_id.display_name)) + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total if record.quantity == total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand." - ) % (record.product_id.display_name)) + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) # Set the quantity to the entered value record.quantity = record.quantity + class BarcodeProduct(models.Model): _name = 'barcode.product' _description = 'Barcode Product' @@ -1841,7 +1889,7 @@ class BarcodeProduct(models.Model): if barcode_product: raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) - + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) if barcode_box: @@ -1855,7 +1903,8 @@ class BarcodeProduct(models.Model): record.product_id.barcode = record.barcode else: raise UserError('Barcode sudah terisi') - + + class CheckKoli(models.Model): _name = 'check.koli' _description = 'Check Koli' @@ -1885,7 +1934,8 @@ class CheckKoli(models.Model): 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' @@ -1929,18 +1979,18 @@ class ScanKoli(models.Model): 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 + 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!')) @@ -1951,7 +2001,7 @@ class ScanKoli(models.Model): existing_koli = self.search([ ('picking_id', '=', record.picking_id.id), ('koli_id', '=', record.koli_id.id), - ('id', '!=', record.id) + ('id', '!=', record.id) ]) if existing_koli: raise ValidationError(f"⚠️ Koli '{record.koli_id.display_name}' sudah discan untuk picking ini!") @@ -1974,22 +2024,23 @@ class ScanKoli(models.Model): 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.")) - + 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') + @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: + 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 @@ -1998,7 +2049,7 @@ class ScanKoli(models.Model): if not scan.picking_id: scan.scan_koli_progress = "0/0" continue - + try: all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') if all_scans: @@ -2010,16 +2061,17 @@ class ScanKoli(models.Model): except Exception: # Fallback in case of any error scan.scan_koli_progress = "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)]) @@ -2033,22 +2085,22 @@ class ScanKoli(models.Model): 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)]) @@ -2056,21 +2108,23 @@ class ScanKoli(models.Model): 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 + 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)]) + 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.") - 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 - # return remaining_scans class KonfirmKoli(models.Model): _name = 'konfirm.koli' @@ -2099,7 +2153,8 @@ class KonfirmKoli(models.Model): 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' @@ -2112,5 +2167,3 @@ class WarningModalWizard(models.TransientModel): if self.picking_id: return self.picking_id.with_context(skip_koli_check=True).button_validate() return {'type': 'ir.actions.act_window_close'} - - -- cgit v1.2.3 From b00d3caf6cd21ae74872031536ceb2f1ff570316 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 5 May 2025 11:05:13 +0700 Subject: (andri) NPWP di SO menjadi readonly --- indoteknik_custom/models/sale_order.py | 8 +++++++- indoteknik_custom/views/sale_order.xml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4c48684d..baa72dc0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -204,7 +204,7 @@ class SaleOrder(models.Model): ('nonpkp', 'Non PKP') ], required=True) sppkp = fields.Char(string="SPPKP", required=True, tracking=True) - npwp = fields.Char(string="NPWP", required=True, tracking=True) + npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_npwp') purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total') voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False) applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False) @@ -995,6 +995,12 @@ class SaleOrder(models.Model): # return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)] + @api.depends('partner_id') + def _compute_npwp(self): + for order in self: + partner = order.partner_id.parent_id or order.partner_id + order.npwp = partner.npwp + @api.onchange('partner_id') def onchange_partner_contact(self): parent_id = self.partner_id.parent_id diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 10c60e24..376ccb20 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -99,7 +99,7 @@ - + -- cgit v1.2.3 From 4fc562965d80b3315e58568c42075edf154256dc Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 11:31:32 +0700 Subject: refactor ask approval retur --- indoteknik_custom/models/stock_picking.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1291737e..0b78706c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -947,6 +947,8 @@ class StockPicking(models.Model): if self.env.user.is_accounting: pick.approval_return_status = 'approved' continue + else: + pick.approval_return_status = 'pengajuan1' action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard') -- cgit v1.2.3 From 921bbc2f0b5b82945aebc11e96ba3847c6f2904d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 5 May 2025 11:48:57 +0700 Subject: (andri) Make the SPPKP & Customer Type columns read-only in SO and ensure the data (along with NPWP) is connected to the contact --- indoteknik_custom/models/sale_order.py | 13 +++++++------ indoteknik_custom/views/sale_order.xml | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index baa72dc0..0711e33a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -202,9 +202,9 @@ class SaleOrder(models.Model): customer_type = fields.Selection([ ('pkp', 'PKP'), ('nonpkp', 'Non PKP') - ], required=True) - sppkp = fields.Char(string="SPPKP", required=True, tracking=True) - npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_npwp') + ], required=True, compute='_compute_partner_field') + sppkp = fields.Char(string="SPPKP", required=True, tracking=True, compute='_compute_partner_field') + npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_partner_field') purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total') voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False) applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False) @@ -994,12 +994,13 @@ class SaleOrder(models.Model): # return [('id', 'not in', order_ids)] # return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)] - @api.depends('partner_id') - def _compute_npwp(self): + def _compute_partner_field(self): for order in self: partner = order.partner_id.parent_id or order.partner_id - order.npwp = partner.npwp + order.npwp = partner.npwp + order.sppkp = partner.sppkp + order.customer_type = partner.customer_type @api.onchange('partner_id') def onchange_partner_contact(self): diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 376ccb20..633ac6d1 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -98,9 +98,9 @@ - + - + -- cgit v1.2.3 From fcc0596b649ef53a6280530b60c89a2a0dc0ebec Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 17:01:05 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0b78706c..fadcc3c2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1203,7 +1203,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date): + if not picking.so_lama and invoice and (not picking.date_doc_kirim or not invoice.invoice_date): raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From 5b78ec72f001ad0d5ccde1269d1ec86418fb7339 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 17:02:16 +0700 Subject: push --- indoteknik_custom/models/sale_order.py | 47 ++++++++++++++++++++++++++++++++++ indoteknik_custom/models/stock_move.py | 1 + indoteknik_custom/views/sale_order.xml | 11 ++++++++ 3 files changed, 59 insertions(+) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b0e17a3a..8af34c75 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -247,6 +247,51 @@ 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)]") + hold_outgoing = fields.Boolean('Hold Outgoing SO') + state_ask_cancel = fields.Selection([ + ('hold', 'Hold'), + ('approve', 'Approve') + ], tracking=True, string='State Cancel', copy=False) + + def ask_retur_cancel_purchasing(self): + for rec in self: + if self.env.user.has_group('indoteknik_custom.group_role_purchasing'): + rec.state_ask_cancel = 'approve' + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Persetujuan Diberikan', + 'message': 'Proses cancel sudah disetujui', + 'type': 'success', + 'sticky': True + } + } + else: + rec.state_ask_cancel = 'hold' + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Menunggu Persetujuan', + 'message': 'Tim Purchasing akan memproses permintaan Anda', + 'type': 'warning', + 'sticky': False + } + } + + def hold_unhold_qty_outgoing_so(self): + pickings = self.env['stock.picking'].search([('sale_id', '=', self.id), ('state', 'not in', ['cancel','done']), ('name', 'ilike', 'BU/PICK/%')]) + products = self.order_line.mapped('product_id') + for picking in pickings: + for line in picking.move_ids_without_package: + if line.product_id in products: + if line.hold_outgoingg == True: + line.hold_outgoingg = False + self.hold_outgoing = False + else: + line.hold_outgoingg = True + self.hold_outgoing = True def _validate_uniform_taxes(self): for order in self: @@ -1392,6 +1437,8 @@ class SaleOrder(models.Model): def action_cancel(self): # TODO stephan prevent cancel if have invoice, do, and po + if self.state_ask_cancel != 'approve': + raise UserError("Anda harus approval purchasing terlebih dahulu") main_parent = self.partner_id.get_main_parent() if self._name != 'sale.order': return super(SaleOrder, self).action_cancel() diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 3c765244..90ab30a4 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -14,6 +14,7 @@ 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') + hold_outgoingg = fields.Boolean('Hold Outgoing', default=False) # @api.model_create_multi # def create(self, vals_list): diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 79a095fb..40a57203 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -16,6 +16,16 @@ type="object" attrs="{'invisible': [('approval_status', '=', ['approved'])]}" /> +