From 1c5eaef25d30a2f760afaad4f5329a87cc4da3c1 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Wed, 26 Feb 2025 16:30:42 +0700 Subject: CR repeat order --- indoteknik_api/models/sale_order.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/indoteknik_api/models/sale_order.py b/indoteknik_api/models/sale_order.py index 727379c5..0b9881ec 100644 --- a/indoteknik_api/models/sale_order.py +++ b/indoteknik_api/models/sale_order.py @@ -104,11 +104,28 @@ class SaleOrder(models.Model): data.update(data_with_detail) else: data_with_detail = { + 'products': [], 'address': { 'customer': res_users.api_address_response(sale_order.partner_id), } } data.update(data_with_detail) + for line in sale_order.order_line: + product = self.env['product.product'].api_single_response(line.product_id) + product['price'] = { + 'price': line.price_unit, + 'discount_percentage': line.discount, + 'price_discount': line.price_reduce_taxexcl, + 'subtotal': line.price_subtotal + } + product['quantity'] = line.product_uom_qty + product['available_quantity'] = line.product_available_quantity + for data_v2 in sale_order.fulfillment_line_v2: + product_v2 = self.env['product.product'].api_single_response(data_v2.product_id) + if product['id'] == product_v2['id']: + product['so_qty'] = data_v2.so_qty + product['reserved_stock_qty'] = data_v2.reserved_stock_qty + data_with_detail['products'].append(product) return data -- cgit v1.2.3 From d416d5b06fbc4cb7b9c5c2ee2bf2b356faad3fe9 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 28 Feb 2025 15:34:10 +0700 Subject: add status --- indoteknik_api/controllers/api_v1/sale_order.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index a7e027c8..b73798b0 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -85,7 +85,8 @@ class SaleOrder(controller.Controller): 'site': [], 'limit': ['default:0', 'number'], 'offset': ['default:0', 'number'], - 'context': [] + 'context': [], + 'status': [] }) limit = params['value']['limit'] offset = params['value']['offset'] @@ -116,7 +117,23 @@ class SaleOrder(controller.Controller): domain += [ ('partner_id.site_id.name', 'ilike', '%' + site + '%') ] - + if params['value']['status']: + if params['value']['status'] == 'quotation': + domain += [('state', '=', 'draft')] + elif params['value']['status'] == 'cancel': + domain += [('state', '=', 'cancel')] + elif params['value']['status'] == 'diterima': + domain += [('state', '=', 'draft')] + domain += [('is_continue_transaction', '=', True)] + domain += [('approval_status', 'in', ['pengajuan1', 'pengajuan2'])] + elif params['value']['status'] == 'diproses': + domain += [('state', '=', 'sale')] + domain += [('picking_ids.state', 'in', ['draft', 'waiting', 'confirmed', 'assigned'])] + elif params['value']['status'] == 'dikirim': + domain += [('state', '=', 'sale')] + domain += [('picking_ids.state', 'not in', ['draft', 'waiting', 'confirmed', 'assigned', 'cancel'])] + elif params['value']['status'] == 'selesai': + domain += [('state', '=', 'done')] sale_orders = request.env['sale.order'].search( domain, offset=offset, limit=limit) data = { -- cgit v1.2.3 From bfb7d3b45bbf780fa77b9abc8a0f1860a50da57c Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 3 Mar 2025 16:38:40 +0700 Subject: update code --- indoteknik_api/controllers/api_v1/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index b73798b0..8723c738 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -124,7 +124,7 @@ class SaleOrder(controller.Controller): domain += [('state', '=', 'cancel')] elif params['value']['status'] == 'diterima': domain += [('state', '=', 'draft')] - domain += [('is_continue_transaction', '=', True)] + # domain += [('is_continue_transaction', '=', True)] domain += [('approval_status', 'in', ['pengajuan1', 'pengajuan2'])] elif params['value']['status'] == 'diproses': domain += [('state', '=', 'sale')] -- cgit v1.2.3 From d7285a4c58256bbadcdd72dc5afbba2cc0b2b491 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 10 Mar 2025 11:21:30 +0700 Subject: add sort filter --- indoteknik_api/controllers/api_v1/sale_order.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index 5914a23c..bf03e04d 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -86,7 +86,8 @@ class SaleOrder(controller.Controller): 'limit': ['default:0', 'number'], 'offset': ['default:0', 'number'], 'context': [], - 'status': [] + 'status': [], + 'sort': [], }) limit = params['value']['limit'] offset = params['value']['offset'] @@ -117,6 +118,7 @@ class SaleOrder(controller.Controller): domain += [ ('partner_id.site_id.name', 'ilike', '%' + site + '%') ] + if params['value']['status']: if params['value']['status'] == 'quotation': domain += [('state', '=', 'draft')] @@ -134,8 +136,18 @@ class SaleOrder(controller.Controller): domain += [('picking_ids.state', 'not in', ['draft', 'waiting', 'confirmed', 'assigned', 'cancel'])] elif params['value']['status'] == 'selesai': domain += [('state', '=', 'done')] + + # Sorting + order = None + if params['value']['sort']: + if params['value']['sort'] == 'asc': + order = 'amount_total asc' + elif params['value']['sort'] == 'desc': + order = 'amount_total desc' + sale_orders = request.env['sale.order'].search( - domain, offset=offset, limit=limit) + domain, offset=offset, limit=limit, order=order) + data = { 'sale_order_total': request.env['sale.order'].search_count(domain), 'sale_orders': [request.env['sale.order'].api_v1_single_response(x) for x in sale_orders] -- cgit v1.2.3 From 61fc24442212691483873fa85dc3eacad8b253be Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Wed, 12 Mar 2025 09:38:53 +0700 Subject: fix transaction --- indoteknik_api/controllers/api_v1/sale_order.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index bf03e04d..3219fc07 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -88,6 +88,8 @@ class SaleOrder(controller.Controller): 'context': [], 'status': [], 'sort': [], + 'startDate': [], + 'endDate': [], }) limit = params['value']['limit'] offset = params['value']['offset'] @@ -145,6 +147,19 @@ class SaleOrder(controller.Controller): elif params['value']['sort'] == 'desc': order = 'amount_total desc' + # Filter berdasarkan tanggal order + if params['value']['startDate'] and params['value']['endDate']: + try: + start_date = datetime.strptime(params['value']['startDate'], '%d/%m/%Y').strftime('%Y-%m-%d 00:00:00') + end_date = datetime.strptime(params['value']['endDate'], '%d/%m/%Y').strftime('%Y-%m-%d 23:59:59') + + domain.append(('date_order', '>=', start_date)) + domain.append(('date_order', '<=', end_date)) + except ValueError: + return self.response(code=400, description="Invalid date format. Use 'DD/MM/YYYY'.") + + + sale_orders = request.env['sale.order'].search( domain, offset=offset, limit=limit, order=order) -- cgit v1.2.3 From 84a7a5fde0f5be9c24a5b324087b8726b1b8f01a Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Wed, 12 Mar 2025 16:59:54 +0700 Subject: add sorting dari yg terbaru --- indoteknik_api/models/sale_order.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/indoteknik_api/models/sale_order.py b/indoteknik_api/models/sale_order.py index 0b9881ec..6debf762 100644 --- a/indoteknik_api/models/sale_order.py +++ b/indoteknik_api/models/sale_order.py @@ -28,12 +28,19 @@ class SaleOrder(models.Model): 'date_order': self.env['rest.api'].datetime_to_str(sale_order.date_order, '%d/%m/%Y %H:%M:%S'), 'pickings': [] } - for picking in sale_order.picking_ids: + # Urutkan picking berdasarkan create_date descending + sorted_pickings = sale_order.picking_ids.sorted(key=lambda p: p.create_date, reverse=True) + + for picking in sorted_pickings: data['pickings'].append({ 'id': picking.id, 'name': picking.name, 'tracking_number': picking.delivery_tracking_no or '', + 'carrier_name': picking.carrier_id.name or '', 'delivered': picking.waybill_id.delivered or picking.driver_arrival_date != False or picking.sj_return_date != False, + 'date': self.env['rest.api'].datetime_to_str(picking.driver_departure_date, '%d/%m/%Y'), + 'eta': picking.generate_eta_delivery(), + 'service_type': sale_order.delivery_service_type or '', }) if sale_order.state == 'cancel': data['status'] = 'cancel' -- cgit v1.2.3 From 431229f2a6f1203fbdfe470229e55da8ebd3ea01 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 14 Mar 2025 16:17:10 +0700 Subject: fix code --- indoteknik_api/models/sale_order.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/indoteknik_api/models/sale_order.py b/indoteknik_api/models/sale_order.py index 6debf762..4fc96386 100644 --- a/indoteknik_api/models/sale_order.py +++ b/indoteknik_api/models/sale_order.py @@ -20,12 +20,14 @@ class SaleOrder(models.Model): 'amount_untaxed': sale_order.amount_untaxed, 'amount_tax': sale_order.amount_tax, 'amount_total': sale_order.grand_total, + 'amount_discount': sale_order.amount_voucher_shipping_disc, 'purchase_order_name': sale_order.partner_purchase_order_name or sale_order.client_order_ref, 'purchase_order_file': True if sale_order.partner_purchase_order_file else False, 'invoice_count': sale_order.invoice_count, 'status': 'draft', 'approval_step': APPROVAL_STEP[sale_order.web_approval] if sale_order.web_approval else 0, 'date_order': self.env['rest.api'].datetime_to_str(sale_order.date_order, '%d/%m/%Y %H:%M:%S'), + 'payment_type': sale_order.payment_type, 'pickings': [] } # Urutkan picking berdasarkan create_date descending @@ -41,6 +43,7 @@ class SaleOrder(models.Model): 'date': self.env['rest.api'].datetime_to_str(picking.driver_departure_date, '%d/%m/%Y'), 'eta': picking.generate_eta_delivery(), 'service_type': sale_order.delivery_service_type or '', + 'weight_total': picking.weight or 0, }) if sale_order.state == 'cancel': data['status'] = 'cancel' -- cgit v1.2.3 From deba962d7368a5c4e30441b5a640102608e3dde6 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Sat, 31 May 2025 08:56:21 +0700 Subject: fix repeat order --- indoteknik_api/controllers/api_v1/sale_order.py | 45 ++++++++++++++++--------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index 39fa0e13..e87b357e 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -136,24 +136,39 @@ class SaleOrder(controller.Controller): ('partner_id.site_id.name', 'ilike', '%' + site + '%') ] - if params['value']['status']: - if params['value']['status'] == 'quotation': + status = params['value'].get('status') + if status: + if status == 'quotation': domain += [('state', '=', 'draft')] - elif params['value']['status'] == 'cancel': + domain += [('approval_status', '=', False)] + + elif status == 'cancel': domain += [('state', '=', 'cancel')] - elif params['value']['status'] == 'diterima': - domain += [('state', '=', 'draft')] - # domain += [('is_continue_transaction', '=', True)] - domain += [('approval_status', 'in', ['pengajuan1', 'pengajuan2'])] - elif params['value']['status'] == 'diproses': - domain += [('state', '=', 'sale')] - domain += [('picking_ids.state', 'in', ['draft', 'waiting', 'confirmed', 'assigned'])] - elif params['value']['status'] == 'dikirim': - domain += [('state', '=', 'sale')] - domain += [('picking_ids.state', 'not in', ['draft', 'waiting', 'confirmed', 'assigned', 'cancel'])] - elif params['value']['status'] == 'selesai': + + elif status == 'diterima': + domain += [ + ('state', '=', 'draft'), + ('approval_status', 'in', ['pengajuan1', 'pengajuan2']), + ] + + elif status == 'dikirim': + domain += [ + ('state', '=', 'sale'), + ('picking_ids.state', 'in', ['done']) + ] + + elif status == 'diproses': + domain += [ + ('state', '=', 'sale'), + ('picking_ids.state', 'in', ['draft', 'waiting', 'confirmed', 'assigned']) + ] + + elif status == 'selesai': domain += [('state', '=', 'done')] + elif status == 'all': + domain += [] + # Sorting order = None if params['value']['sort']: @@ -190,7 +205,7 @@ class SaleOrder(controller.Controller): def partner_get_sale_order_detail(self, **kw): params = self.get_request_params(kw, { 'partner_id': ['number'], - 'id': ['number'] + 'id': ['number'], }) if not params['valid']: return self.response(code=400, description=params) -- cgit v1.2.3 From 5966b9f4b5430caaedfa9fbd6b30fd047a497fbf Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 26 Jun 2025 10:33:33 +0700 Subject: reset voucher usage limit when canceled --- indoteknik_api/controllers/api_v1/voucher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_api/controllers/api_v1/voucher.py b/indoteknik_api/controllers/api_v1/voucher.py index 9ffeeace..610bcd60 100644 --- a/indoteknik_api/controllers/api_v1/voucher.py +++ b/indoteknik_api/controllers/api_v1/voucher.py @@ -74,7 +74,7 @@ class Voucher(controller.Controller): partner_voucher_orders = [] for order in voucher.order_ids: - if order.partner_id.id == user.partner_id.id: + if order.partner_id.id == user.partner_id.id and order.state != 'cancel': partner_voucher_orders.append(order) if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user: -- cgit v1.2.3 From 71078342e5199834e3c0bc9d9844a426bb156260 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 26 Jun 2025 13:40:20 +0700 Subject: reset voucher usage limit when canceled --- indoteknik_api/controllers/api_v1/voucher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_api/controllers/api_v1/voucher.py b/indoteknik_api/controllers/api_v1/voucher.py index 610bcd60..0338360b 100644 --- a/indoteknik_api/controllers/api_v1/voucher.py +++ b/indoteknik_api/controllers/api_v1/voucher.py @@ -74,7 +74,7 @@ class Voucher(controller.Controller): partner_voucher_orders = [] for order in voucher.order_ids: - if order.partner_id.id == user.partner_id.id and order.state != 'cancel': + if order.partner_id.id == user.partner_id.id and order.state != 'cancel' and (order.payment_status or order.payment_status is None): partner_voucher_orders.append(order) if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user: -- cgit v1.2.3 From b1c7b43c51c670e42a0dd2399139fbd9a600f121 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 24 Jul 2025 16:16:36 +0700 Subject: is disc item from web --- indoteknik_custom/models/sale_order_line.py | 14 ++++++++++++++ indoteknik_custom/views/sale_order.xml | 1 + 2 files changed, 15 insertions(+) diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 5e9fc362..f2799319 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -1,6 +1,9 @@ from odoo import fields, models, api, _ from odoo.exceptions import UserError from datetime import datetime, timedelta +import logging + +__logger = logging.getLogger(__name__) class SaleOrderLine(models.Model): @@ -49,6 +52,17 @@ class SaleOrderLine(models.Model): qty_free_bu = fields.Float(string='Free BU', compute='_get_qty_free_bandengan') desc_updatable = fields.Boolean(string='desc boolean', default=True, compute='_get_desc_updatable') + is_has_disc = fields.Boolean('FlashSale Item', compute='_compute_is_has_disc', default=False) + + @api.depends('discount', 'order_id.source_id') + def _compute_is_has_disc(self): + for line in self: + line.is_has_disc = ( + line.discount > 0 and + line.order_id.source_id and + line.order_id.source_id.id == 59 + ) + def _get_outgoing_incoming_moves(self): outgoing_moves = self.env['stock.move'] incoming_moves = self.env['stock.move'] diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 346dc0f8..532fccc2 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -289,6 +289,7 @@ + Date: Sat, 26 Jul 2025 09:23:07 +0700 Subject: push --- indoteknik_custom/views/refund_sale_order.xml | 199 -------------------------- 1 file changed, 199 deletions(-) delete mode 100644 indoteknik_custom/views/refund_sale_order.xml diff --git a/indoteknik_custom/views/refund_sale_order.xml b/indoteknik_custom/views/refund_sale_order.xml deleted file mode 100644 index 4f791722..00000000 --- a/indoteknik_custom/views/refund_sale_order.xml +++ /dev/null @@ -1,199 +0,0 @@ - - - - - refund.sale.order.tree - refund.sale.order - - - - - - - - - - - - - - - - - - - - - - - - refund.sale.order.form - refund.sale.order - -
-
-
- -
- -
- - - -

- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
-
- - - Refund Sales Order - refund.sale.order - tree,form - - - - -
-- cgit v1.2.3 From ae201ad5ac392a75da087a5e1215f9b8fcd50dba Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 26 Jul 2025 12:35:36 +0700 Subject: push --- indoteknik_custom/models/sale_order_line.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index f2799319..bc1fcd09 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -54,14 +54,29 @@ class SaleOrderLine(models.Model): is_has_disc = fields.Boolean('FlashSale Item', compute='_compute_is_has_disc', default=False) - @api.depends('discount', 'order_id.source_id') + @api.depends('product_id', 'price_unit', 'order_id.source_id') def _compute_is_has_disc(self): + website_source_id = 59 + excluded_pricelist_ids = [17022, 17027, 17026, 17025, 17024, 17023] + for line in self: - line.is_has_disc = ( - line.discount > 0 and - line.order_id.source_id and - line.order_id.source_id.id == 59 - ) + line.is_has_disc = False # default + + # Step 1: Bukan dari website? Skip + if not line.order_id.source_id or line.order_id.source_id.id != website_source_id: + continue + + # Step 2: Ambil semua pricelist item berdasarkan produk + pricelist_items = self.env['product.pricelist.item'].search([ + ('product_id', '=', line.product_id.id), + ('pricelist_id', 'not in', excluded_pricelist_ids), + ]) + + # Step 3: Jika ada pricelist_items, tandai sebagai flashsale + if pricelist_items: + line.is_has_disc = True + elif not pricelist_items: + line.is_has_disc = False def _get_outgoing_incoming_moves(self): outgoing_moves = self.env['stock.move'] -- cgit v1.2.3 From 0c5486d6e67cd8bab4913dd177ed9ddf0be36d60 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 26 Jul 2025 13:32:59 +0700 Subject: fix user cannot access delivery tukar guling --- indoteknik_custom/models/tukar_guling.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 43bc156e..26b98e32 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -386,14 +386,23 @@ class TukarGuling(models.Model): def action_view_picking(self): self.ensure_one() - action = self.env.ref('stock.action_picking_tree_all').read()[0] - pickings = self.picking_ids - if len(pickings) > 1: - action['domain'] = [('id', 'in', pickings.ids)] - elif pickings: - action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')] - action['res_id'] = pickings.id - return action + + # picking_origin = f"Return of {self.operations.name}" + returs = self.env['stock.picking'].search([ + ('tukar_guling_id', '=', self.id), + ]) + + if not returs: + raise UserError("Doc Retrun Not Found") + + return { + 'type': 'ir.actions.act_window', + 'name': 'Delivery Pengajuan Retur SO', + 'res_model': 'stock.picking', + 'view_mode': 'tree,form', + 'domain': [('id', 'in', returs.ids)], + 'target': 'current', + } def action_draft(self): """Reset to draft state""" -- cgit v1.2.3 From 83c6a567c1e2f1b8c281ea405732a286cb20a8c9 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 26 Jul 2025 13:34:00 +0700 Subject: fix user cannot access delivery tukar guling --- indoteknik_custom/models/tukar_guling_po.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 14f2cc96..fced7fdb 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -320,14 +320,23 @@ class TukarGulingPO(models.Model): def action_view_picking(self): self.ensure_one() - action = self.env.ref('stock.action_picking_tree_all').read()[0] - pickings = self.po_picking_ids - if len(pickings) > 1: - action['domain'] = [('id', 'in', pickings.ids)] - elif pickings: - action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')] - action['res_id'] = pickings.id - return action + + # picking_origin = f"Return of {self.operations.name}" + returs = self.env['stock.picking'].search([ + ('tukar_guling_po_id', '=', self.id), + ]) + + if not returs: + raise UserError("Doc Retrun Not Found") + + return { + 'type': 'ir.actions.act_window', + 'name': 'Delivery Pengajuan Retur PO', + 'res_model': 'stock.picking', + 'view_mode': 'tree,form', + 'domain': [('id', 'in', returs.ids)], + 'target': 'current', + } def action_draft(self): """Reset to draft state""" -- cgit v1.2.3 From aeb450314a0440806ceb300c71c5776d42289ad4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 26 Jul 2025 21:02:52 +0700 Subject: push --- indoteknik_api/controllers/api_v1/sale_order.py | 31 ++++++++++++++++++++----- indoteknik_custom/models/sale_order_line.py | 28 +++------------------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index e1c643e5..fbf1dc15 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -470,28 +470,47 @@ class SaleOrder(controller.Controller): source = params['value']['source'] carts = user_cart.get_product_by_user(user_id=user_id, selected=True, source=source) + excluded_pricelist_ids = [ + int(config.get_param('product.pricelist.tier1_v2')), + int(config.get_param('product.pricelist.tier2_v2')), + int(config.get_param('product.pricelist.tier3_v2')), + int(config.get_param('product.pricelist.tier4_v2')), + ] + promotions = [] for cart in carts: if cart['cart_type'] == 'product': - order_line = request.env['sale.order.line'].create({ + cart_pricelist_id = cart['price'].get('pricelist_id') + + is_has_disc = False + if cart_pricelist_id and cart_pricelist_id not in excluded_pricelist_ids: + is_has_disc = True + elif params['value'].get('flash_sale'): # fallback jika pricelist_id tidak tersedia + is_has_disc = False + + line_vals = { 'company_id': 1, 'order_id': sale_order.id, 'product_id': cart['id'], 'product_uom_qty': cart['quantity'], - 'product_available_quantity': cart['available_quantity'] - }) + 'product_available_quantity': cart['available_quantity'], + 'price_unit': cart['price']['price'], + 'discount': cart['price']['discount_percentage'], + 'is_has_disc': is_has_disc + } + + order_line = request.env['sale.order.line'].create(line_vals) order_line.product_id_change() order_line.weight = order_line.product_id.weight order_line.onchange_vendor_id() - order_line.price_unit = cart['price']['price'] - order_line.discount = cart['price']['discount_percentage'] + elif cart['cart_type'] == 'promotion': promotions.append({ 'order_id': sale_order.id, 'program_line_id': cart['id'], 'quantity': cart['quantity'] }) - + sale_order._compute_etrts_date() request.env['sale.order.promotion'].create(promotions) diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index bc1fcd09..ff2e9009 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -2,8 +2,9 @@ from odoo import fields, models, api, _ from odoo.exceptions import UserError from datetime import datetime, timedelta import logging +from odoo.tools.float_utils import float_compare -__logger = logging.getLogger(__name__) +_logger = logging.getLogger(__name__) class SaleOrderLine(models.Model): @@ -52,31 +53,8 @@ class SaleOrderLine(models.Model): qty_free_bu = fields.Float(string='Free BU', compute='_get_qty_free_bandengan') desc_updatable = fields.Boolean(string='desc boolean', default=True, compute='_get_desc_updatable') - is_has_disc = fields.Boolean('FlashSale Item', compute='_compute_is_has_disc', default=False) + is_has_disc = fields.Boolean('FlashSale Item', default=False) - @api.depends('product_id', 'price_unit', 'order_id.source_id') - def _compute_is_has_disc(self): - website_source_id = 59 - excluded_pricelist_ids = [17022, 17027, 17026, 17025, 17024, 17023] - - for line in self: - line.is_has_disc = False # default - - # Step 1: Bukan dari website? Skip - if not line.order_id.source_id or line.order_id.source_id.id != website_source_id: - continue - - # Step 2: Ambil semua pricelist item berdasarkan produk - pricelist_items = self.env['product.pricelist.item'].search([ - ('product_id', '=', line.product_id.id), - ('pricelist_id', 'not in', excluded_pricelist_ids), - ]) - - # Step 3: Jika ada pricelist_items, tandai sebagai flashsale - if pricelist_items: - line.is_has_disc = True - elif not pricelist_items: - line.is_has_disc = False def _get_outgoing_incoming_moves(self): outgoing_moves = self.env['stock.move'] -- cgit v1.2.3 From d1911795581a7528aef706fd0f26ea7e9b8014c2 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sun, 27 Jul 2025 13:23:20 +0700 Subject: done(?) --- indoteknik_api/controllers/api_v1/sale_order.py | 342 ++++++++++++++---------- 1 file changed, 199 insertions(+), 143 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index fbf1dc15..ccab2827 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -3,6 +3,9 @@ from odoo import http from datetime import datetime, timedelta from odoo.http import request import json +import logging + +_logger = logging.getLogger(__name__) class SaleOrder(controller.Controller): @@ -390,157 +393,210 @@ class SaleOrder(controller.Controller): @http.route(PREFIX_PARTNER + 'sale_order/checkout', auth='public', method=['POST', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized() def create_partner_sale_order(self, **kw): - config = request.env['ir.config_parameter'] - product_pricelist_default_discount_id = int(config.get_param('product.pricelist.tier1_v2')) - user_pricelist = request.env.context.get('user_pricelist').id or False - - params = self.get_request_params(kw, { - 'user_id': ['number'], - 'partner_id': ['number'], - 'partner_shipping_id': ['required', 'number'], - 'partner_invoice_id': ['required', 'number'], - 'order_line': ['required', 'default:[]'], - 'po_number': [], - 'po_file': [], - 'type': [], - 'delivery_amount': ['number', 'default:0'], - 'carrier_id': [], - 'delivery_service_type': [], - 'flash_sale': ['boolean'], - 'note_website': [], - 'voucher': [], - 'source': [], - 'estimated_arrival_days': ['number', 'default:0'], - 'estimated_arrival_days_start': ['number', 'default:0'] - }) - - if not params['valid']: - return self.response(code=400, description=params) + _logger.info("=== START CREATE PARTNER SALE ORDER ===") + + try: + config = request.env['ir.config_parameter'] + product_pricelist_default_discount_id = int(config.get_param('product.pricelist.tier1_v2')) + user_pricelist = request.env.context.get('user_pricelist').id or False + _logger.info( + f"Config - Default Pricelist: {product_pricelist_default_discount_id}, User Pricelist: {user_pricelist}") + + params = self.get_request_params(kw, { + 'user_id': ['number'], + 'partner_id': ['number'], + 'partner_shipping_id': ['required', 'number'], + 'partner_invoice_id': ['required', 'number'], + 'order_line': ['required', 'default:[]'], + 'po_number': [], + 'po_file': [], + 'type': [], + 'delivery_amount': ['number', 'default:0'], + 'carrier_id': [], + 'delivery_service_type': [], + 'flash_sale': ['boolean'], + 'note_website': [], + 'voucher': [], + 'source': [], + 'estimated_arrival_days': ['number', 'default:0'], + 'estimated_arrival_days_start': ['number', 'default:0'] + }) - # Fetch partner details - sales_partner = request.env['res.partner'].browse(params['value']['partner_id']) - partner_invoice = request.env['res.partner'].browse(params['value']['partner_invoice_id']) - main_partner = partner_invoice.get_main_parent() - parameters = { - 'warehouse_id': 8, - 'carrier_id': 1, - 'sales_tax_id': 23, - 'pricelist_id': user_pricelist or product_pricelist_default_discount_id, - 'payment_term_id': 26, - 'team_id': 2, - 'company_id': 1, - 'currency_id': 12, - 'source_id': 59, - 'state': 'draft', - 'picking_policy': 'direct', - 'partner_id': params['value']['partner_id'], - 'partner_shipping_id': params['value']['partner_shipping_id'], - 'real_shipping_id': params['value']['partner_shipping_id'], - 'partner_invoice_id': main_partner.id, - 'real_invoice_id': params['value']['partner_invoice_id'], - 'partner_purchase_order_name': params['value']['po_number'], - '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'], - 'delivery_service_type': params['value']['delivery_service_type'], - 'flash_sale': params['value']['flash_sale'], - 'note_website': params['value']['note_website'], - 'customer_type': sales_partner.customer_type if sales_partner else 'nonpkp', # Get Customer Type from partner - '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': 11314 # User ID: Boy Revandi - } - - sales_partner = request.env['res.partner'].browse(parameters['partner_id']) - if sales_partner and sales_partner.user_id and sales_partner.user_id.id not in [25]: # 25: System - parameters['user_id'] = sales_partner.user_id.id - - if params['value']['type'] == 'sale_order': - parameters['approval_status'] = 'pengajuan1' - sale_order = request.env['sale.order'].with_context(from_website_checkout=True).create([parameters]) - sale_order.onchange_partner_contact() - - user_id = params['value']['user_id'] - user_cart = request.env['website.user.cart'] - source = params['value']['source'] - carts = user_cart.get_product_by_user(user_id=user_id, selected=True, source=source) - - excluded_pricelist_ids = [ - int(config.get_param('product.pricelist.tier1_v2')), - int(config.get_param('product.pricelist.tier2_v2')), - int(config.get_param('product.pricelist.tier3_v2')), - int(config.get_param('product.pricelist.tier4_v2')), - ] + _logger.info(f"Raw input params: {kw}") + _logger.info(f"Processed params: {params}") - promotions = [] - for cart in carts: - if cart['cart_type'] == 'product': - cart_pricelist_id = cart['price'].get('pricelist_id') + if not params['valid']: + _logger.error(f"Invalid params: {params}") + return self.response(code=400, description=params) - is_has_disc = False - if cart_pricelist_id and cart_pricelist_id not in excluded_pricelist_ids: - is_has_disc = True - elif params['value'].get('flash_sale'): # fallback jika pricelist_id tidak tersedia - is_has_disc = False + # Fetch partner details + sales_partner = request.env['res.partner'].browse(params['value']['partner_id']) + partner_invoice = request.env['res.partner'].browse(params['value']['partner_invoice_id']) + main_partner = partner_invoice.get_main_parent() + _logger.info( + f"Partner Info - Sales: {sales_partner.id}, Invoice: {partner_invoice.id}, Main: {main_partner.id}") - line_vals = { - 'company_id': 1, - 'order_id': sale_order.id, - 'product_id': cart['id'], - 'product_uom_qty': cart['quantity'], - 'product_available_quantity': cart['available_quantity'], - 'price_unit': cart['price']['price'], - 'discount': cart['price']['discount_percentage'], - 'is_has_disc': is_has_disc - } + parameters = { + 'warehouse_id': 8, + 'carrier_id': 1, + 'sales_tax_id': 23, + 'pricelist_id': user_pricelist or product_pricelist_default_discount_id, + 'payment_term_id': 26, + 'team_id': 2, + 'company_id': 1, + 'currency_id': 12, + 'source_id': 59, + 'state': 'draft', + 'picking_policy': 'direct', + 'partner_id': params['value']['partner_id'], + 'partner_shipping_id': params['value']['partner_shipping_id'], + 'real_shipping_id': params['value']['partner_shipping_id'], + 'partner_invoice_id': main_partner.id, + 'real_invoice_id': params['value']['partner_invoice_id'], + 'partner_purchase_order_name': params['value']['po_number'], + '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'], + 'delivery_service_type': params['value']['delivery_service_type'], + 'flash_sale': params['value']['flash_sale'], + 'note_website': params['value']['note_website'], + 'customer_type': sales_partner.customer_type if sales_partner else 'nonpkp', + 'npwp': sales_partner.npwp or '0', + 'sppkp': sales_partner.sppkp, + 'email': sales_partner.email, + 'user_id': 11314 + } + _logger.info(f"Order parameters: {parameters}") + + sales_partner = request.env['res.partner'].browse(parameters['partner_id']) + if sales_partner and sales_partner.user_id and sales_partner.user_id.id not in [25]: + parameters['user_id'] = sales_partner.user_id.id + _logger.info(f"Updated user_id from partner: {parameters['user_id']}") + + if params['value']['type'] == 'sale_order': + parameters['approval_status'] = 'pengajuan1' + _logger.info("Setting approval_status to 'pengajuan1'") + + sale_order = request.env['sale.order'].with_context(from_website_checkout=True).create([parameters]) + sale_order.onchange_partner_contact() + _logger.info(f"Created SO: {sale_order.id} - {sale_order.name}") + + user_id = params['value']['user_id'] + user_cart = request.env['website.user.cart'] + source = params['value']['source'] + _logger.info(f"Getting cart for user: {user_id}, source: {source}") + + carts = user_cart.get_product_by_user(user_id=user_id, selected=True, source=source) + _logger.info(f"Found {len(carts)} cart items") + + promotions = [] + for idx, cart in enumerate(carts, 1): + _logger.info(f"\n=== Processing Cart Item {idx}/{len(carts)} ===") + _logger.info(f"Full cart data: {cart}") + + if cart['cart_type'] == 'product': + product = request.env['product.product'].browse(cart['id']) + _logger.info(f"Product: {product.id} - {product.name}") + _logger.info(f"Cart Price Data: {cart['price']}") + + # Determine discount status based on: + # 1. has_flashsale flag from cart data + # 2. discount percentage > 0 + # 3. global flash sale parameter + is_flash_sale_item = cart.get('has_flashsale', False) + discount_percent = float(cart['price'].get('discount_percentage', 0)) + global_flash_sale = params['value'].get('flash_sale', False) - order_line = request.env['sale.order.line'].create(line_vals) - order_line.product_id_change() - order_line.weight = order_line.product_id.weight - order_line.onchange_vendor_id() + is_has_disc = False - elif cart['cart_type'] == 'promotion': - promotions.append({ - 'order_id': sale_order.id, - 'program_line_id': cart['id'], - 'quantity': cart['quantity'] - }) + # Item is considered discounted if: + # - It's specifically marked as flash sale item, OR + # - It has significant discount (>0%) and not affected by global flash sale + if is_flash_sale_item: + is_has_disc = True + _logger.info("Item is flash sale product - marked as discounted") + elif discount_percent > 0 and not global_flash_sale: + is_has_disc = True + _logger.info(f"Item has discount {discount_percent}% - marked as discounted") + elif global_flash_sale: + _logger.info("Global flash sale active but item not eligible - not marked as discounted") + + _logger.info(f"Final is_has_disc: {is_has_disc}") + + order_line = request.env['sale.order.line'].create({ + 'company_id': 1, + 'order_id': sale_order.id, + 'product_id': product.id, + 'product_uom_qty': cart['quantity'], + 'product_available_quantity': cart['available_quantity'], + 'price_unit': cart['price']['price'], + 'discount': discount_percent, + 'is_has_disc': is_has_disc + }) + _logger.info(f"Created order line: {order_line.id}") + + order_line.product_id_change() + order_line.weight = order_line.product_id.weight + order_line.onchange_vendor_id() + _logger.info(f"After onchanges - Price: {order_line.price_unit}, Disc: {order_line.discount}") + + elif cart['cart_type'] == 'promotion': + promotions.append({ + 'order_id': sale_order.id, + 'program_line_id': cart['id'], + 'quantity': cart['quantity'] + }) + _logger.info(f"Added promotion: {cart['id']}") + + _logger.info("Processing promotions...") + sale_order._compute_etrts_date() + request.env['sale.order.promotion'].create(promotions) + + if len(promotions) > 0: + _logger.info(f"Applying {len(promotions)} promotions") + sale_order.apply_promotion_program() + sale_order.add_free_product(promotions) + + voucher_code = params['value']['voucher'] + if voucher_code: + _logger.info(f"Processing voucher: {voucher_code}") + voucher = request.env['voucher'].search( + [('code', '=', voucher_code), ('apply_type', 'in', ['all', 'brand'])], limit=1) + voucher_shipping = request.env['voucher'].search( + [('code', '=', voucher_code), ('apply_type', 'in', ['shipping'])], limit=1) + + if voucher and len(promotions) == 0: + _logger.info("Applying regular voucher") + sale_order.voucher_id = voucher.id + sale_order.apply_voucher() + + if voucher_shipping and len(promotions) == 0: + _logger.info("Applying shipping voucher") + sale_order.voucher_shipping_id = voucher_shipping.id + sale_order.apply_voucher_shipping() + + cart_ids = [x['cart_id'] for x in carts] + if sale_order._requires_approval_margin_leader(): + sale_order.approval_status = 'pengajuan2' + _logger.info("Approval status set to 'pengajuan2'") + elif sale_order._requires_approval_margin_manager(): + sale_order.approval_status = 'pengajuan1' + _logger.info("Approval status set to 'pengajuan1'") - sale_order._compute_etrts_date() + sale_order._auto_set_shipping_from_website() + _logger.info("=== END CREATE PARTNER SALE ORDER ===") + return self.response({ + 'id': sale_order.id, + 'name': sale_order.name + }) - request.env['sale.order.promotion'].create(promotions) - - if len(promotions) > 0: - sale_order.apply_promotion_program() - sale_order.add_free_product(promotions) - - voucher_code = params['value']['voucher'] - voucher = request.env['voucher'].search([('code', '=', voucher_code),('apply_type', 'in', ['all', 'brand'])], limit=1) - voucher_shipping = request.env['voucher'].search([('code', '=', voucher_code),('apply_type', 'in', ['shipping'])], limit=1) - if voucher and len(promotions) == 0: - sale_order.voucher_id = voucher.id - sale_order.apply_voucher() - - if voucher_shipping and len(promotions) == 0: - sale_order.voucher_shipping_id = voucher_shipping.id - sale_order.apply_voucher_shipping() - - cart_ids = [x['cart_id'] for x in carts] - if sale_order._requires_approval_margin_leader(): #jika ada error tambahkan kondisi if params['value']['type'] == 'sale_order': - sale_order.approval_status = 'pengajuan2' - elif sale_order._requires_approval_margin_manager(): - sale_order.approval_status = 'pengajuan1' - # user_cart.browse(cart_ids).unlink() - sale_order._auto_set_shipping_from_website() - return self.response({ - 'id': sale_order.id, - 'name': sale_order.name - }) + except Exception as e: + _logger.error(f"Error in create_partner_sale_order: {str(e)}", exc_info=True) + return self.response(code=500, description=str(e)) @http.route(PREFIX_PARTNER + 'sale-order//awb', auth='public', methods=['GET', 'OPTIONS']) @controller.Controller.must_authorized(private=True, private_key='partner_id') -- cgit v1.2.3 From e2c727fc2d1a2c9c3601368df7d4701d9b71fb82 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 28 Jul 2025 08:46:21 +0700 Subject: done(?) --- indoteknik_custom/models/sale_order_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index ff2e9009..428e1a9c 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -53,7 +53,7 @@ class SaleOrderLine(models.Model): qty_free_bu = fields.Float(string='Free BU', compute='_get_qty_free_bandengan') desc_updatable = fields.Boolean(string='desc boolean', default=True, compute='_get_desc_updatable') - is_has_disc = fields.Boolean('FlashSale Item', default=False) + is_has_disc = fields.Boolean('Website Disc?', default=False) def _get_outgoing_incoming_moves(self): -- cgit v1.2.3 From 9a01c7f11c128380c367bb479ef2b33b2c445ea7 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 28 Jul 2025 10:28:29 +0700 Subject: fix validation tukar guling --- indoteknik_custom/models/tukar_guling.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 26b98e32..396e4db4 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -281,11 +281,11 @@ class TukarGuling(models.Model): return True - def _is_already_returned(self, picking): - return self.env['stock.picking'].search_count([ - ('origin', '=', 'Return of %s' % picking.name), - ('state', '!=', 'cancel') - ]) > 0 + # def _is_already_returned(self, picking): + # return self.env['stock.picking'].search_count([ + # ('origin', '=', 'Return of %s' % picking.name), + # ('state', '!=', 'cancel') + # ]) > 0 @api.constrains('return_type', 'operations') def _check_invoice_on_revisi_so(self): @@ -443,8 +443,8 @@ class TukarGuling(models.Model): linked_bu_out = picking.linked_manual_bu_out if linked_bu_out and linked_bu_out.state == 'done': raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT suda Done!") - if self._is_already_returned(self.operations): - raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") + # if self._is_already_returned(self.operations): + # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") if self.operations.picking_type_id.id == 29: for line in self.line_ids: -- cgit v1.2.3 From 84c8e3f8494971a26ed8e303d8584a3caaaa5859 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 28 Jul 2025 10:37:39 +0700 Subject: fix validation tukar guling --- indoteknik_custom/models/tukar_guling_po.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index fced7fdb..11377bc4 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -245,12 +245,12 @@ class TukarGulingPO(models.Model): return True - def _is_already_returned(self, picking): - return self.env['stock.picking'].search_count([ - ('origin', '=', 'Return of %s' % picking.name), - # ('returned_from_id', '=', picking.id), - ('state', 'not in', ['cancel', 'draft']), - ]) > 0 + # def _is_already_returned(self, picking): + # return self.env['stock.picking'].search_count([ + # ('origin', '=', 'Return of %s' % picking.name), + # # ('returned_from_id', '=', picking.id), + # ('state', 'not in', ['cancel', 'draft']), + # ]) > 0 def copy(self, default=None): if default is None: @@ -374,8 +374,8 @@ class TukarGulingPO(models.Model): if pick_id not in [75, 28]: raise UserError("❌ Tidak bisa retur bukan BU/INPUT atau BU/PUT!") - if self._is_already_returned(self.operations): - raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") + # if self._is_already_returned(self.operations): + # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") if self.state != 'draft': raise UserError("Submit hanya bisa dilakukan dari Draft.") -- cgit v1.2.3 From d5eb5c9139f016498cc70d6f241b8597b0b4b06b Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 28 Jul 2025 11:05:50 +0700 Subject: push --- indoteknik_custom/models/sale_order_line.py | 2 +- indoteknik_custom/views/sale_order.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 428e1a9c..64b9f9bc 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -53,7 +53,7 @@ class SaleOrderLine(models.Model): qty_free_bu = fields.Float(string='Free BU', compute='_get_qty_free_bandengan') desc_updatable = fields.Boolean(string='desc boolean', default=True, compute='_get_desc_updatable') - is_has_disc = fields.Boolean('Website Disc?', default=False) + is_has_disc = fields.Boolean('Flash Sale', default=False) def _get_outgoing_incoming_moves(self): diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index c166ecad..79ac3ed9 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -292,7 +292,7 @@ - +
Date: Mon, 28 Jul 2025 14:58:11 +0700 Subject: fix bug manufacturing --- indoteknik_custom/models/mrp_production.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 7977bdf7..91da0597 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -156,7 +156,7 @@ class MrpProduction(models.Model): 'order_id': new_po.id }]) - new_po.button_confirm() + # new_po.button_confirm() self.is_po = True @@ -247,7 +247,7 @@ class CheckBomProduct(models.Model): @api.constrains('production_id', 'product_id') def _check_product_bom_validation(self): for record in self: - if record.production_id.sale_order.state not in ['sale', 'done']: + if record.production_id.sale_order and record.production_id.sale_order.state not in ['sale', 'done']: raise UserError(( "SO harus diconfirm terlebih dahulu." )) @@ -273,13 +273,13 @@ class CheckBomProduct(models.Model): if existing_lines: 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' 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: + if record.quantity > total_qty_in_moves: raise UserError(( "Quantity Product '%s' kurang dari quantity demand." ) % (record.product_id.display_name)) -- cgit v1.2.3 From 74953e4b98abca66898426586907a6ed22ef4139 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 28 Jul 2025 15:20:43 +0700 Subject: (andri) add flag reminder invoices di contact company --- indoteknik_custom/models/res_partner.py | 5 +++++ indoteknik_custom/views/res_partner.xml | 3 +++ 2 files changed, 8 insertions(+) diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 236df16f..d1d4ca02 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -27,6 +27,11 @@ class ResPartner(models.Model): # Referensi supplier_ids = fields.Many2many('user.pengajuan.tempo.line', string="Suppliers") + reminder_invoices = fields.Boolean( + string='Reminder Invoice', + help='Centang jika kontak ini harus menerima email pengingat invoice.', default=False + ) + # informasi perusahaan name_tempo = fields.Many2one('res.partner', string='Nama Perusahaan',tracking=True) industry_id_tempo = fields.Many2one('res.partner.industry', 'Customer Industry', readonly=True) diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index a030a75c..08eca7ea 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -102,6 +102,9 @@ /> + + + 0 -- cgit v1.2.3 From 8628973dfbfc76f3f7fea5a01d5ccad4998f7f3f Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 28 Jul 2025 15:37:22 +0700 Subject: (andri) fix send due invoices reminder --- indoteknik_custom/models/account_move.py | 203 ++++++++++++++++--------------- indoteknik_custom/models/res_partner.py | 2 +- 2 files changed, 108 insertions(+), 97 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 1a6fad1c..14927bec 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -109,102 +109,113 @@ class AccountMove(models.Model): # result.append((move.id, move.display_name)) # return result - # def send_due_invoice_reminder(self): - # today = fields.Date.today() - # target_dates = [ - # today - timedelta(days=7), - # today - timedelta(days=3), - # today, - # today + timedelta(days=3), - # today + timedelta(days=7), - # ] - - # partner = self.env['res.partner'].search([('name', 'ilike', 'BANGUNAN TEKNIK GRUP')], limit=1) - # if not partner: - # _logger.info("Partner tidak ditemukan.") - # return - - # invoices = self.env['account.move'].search([ - # ('move_type', '=', 'out_invoice'), - # ('state', '=', 'posted'), - # ('payment_state', 'not in', ['paid','in_payment', 'reversed']), - # ('invoice_date_due', 'in', target_dates), - # ('partner_id', '=', partner.id), - # ]) - - # _logger.info(f"Invoices tahap 1: {invoices}") - - # invoices = invoices.filtered( - # lambda inv: inv.invoice_payment_term_id and 'tempo' in (inv.invoice_payment_term_id.name or '').lower() - # ) - # _logger.info(f"Invoices tahap 2: {invoices}") - - # if not invoices: - # _logger.info(f"Tidak ada invoice yang due untuk partner: {partner.name}") - # return - - # grouped = {} - # for inv in invoices: - # grouped.setdefault(inv.partner_id, []).append(inv) - - # template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') - - # for partner, invs in grouped.items(): - # if not partner.email: - # _logger.info(f"Partner {partner.name} tidak memiliki email") - # continue - - # invoice_table_rows = "" - # for inv in invs: - # days_to_due = (inv.invoice_date_due - today).days if inv.invoice_date_due else 0 - # invoice_table_rows += f""" - # - # {inv.name} - # {fields.Date.to_string(inv.invoice_date) or '-'} - # {fields.Date.to_string(inv.invoice_date_due) or '-'} - # {days_to_due} - # {formatLang(self.env, inv.amount_total, currency_obj=inv.currency_id)} - # {inv.ref or '-'} - # - # """ - - # subject = f"Reminder Invoice Due - {partner.name}" - # body_html = re.sub( - # r"]*>.*?", - # f"{invoice_table_rows}", - # template.body_html, - # flags=re.DOTALL - # ).replace('${object.name}', partner.name) \ - # .replace('${object.partner_id.name}', partner.name) - # # .replace('${object.email}', partner.email or '') - - # values = { - # 'subject': subject, - # 'email_to': 'andrifebriyadiputra@gmail.com', # Ubah ke partner.email untuk produksi - # 'email_from': 'finance@indoteknik.co.id', - # 'body_html': body_html, - # 'reply_to': f'invoice+account.move_{invs[0].id}@indoteknik.co.id', - # } - - # _logger.info(f"VALUES: {values}") - - # template.send_mail(invs[0].id, force_send=True, email_values=values) - - # # Default System User - # user_system = self.env['res.users'].browse(25) - # system_id = user_system.partner_id.id if user_system else False - # _logger.info(f"System User: {user_system.name} ({user_system.id})") - # _logger.info(f"System User ID: {system_id}") - - # for inv in invs: - # inv.message_post( - # subject=subject, - # body=body_html, - # subtype_id=self.env.ref('mail.mt_note').id, - # author_id=system_id, - # ) - - # _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice") + def send_due_invoice_reminder(self): + today = fields.Date.today() + target_dates = [ + today - timedelta(days=7), + today - timedelta(days=3), + today, + today + timedelta(days=3), + today + timedelta(days=7), + ] + + # Contoh khusus partner tertentu + partner = self.env['res.partner'].search([('name', 'ilike', 'BANGUNAN TEKNIK GRUP')], limit=1) + if not partner: + _logger.info("Partner tidak ditemukan.") + return + + invoices = self.env['account.move'].search([ + ('move_type', '=', 'out_invoice'), + ('state', '=', 'posted'), + ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), + ('invoice_date_due', 'in', target_dates), + ('partner_id', '=', partner.id), + ]) + + _logger.info(f"Invoices tahap 1: {invoices}") + + invoices = invoices.filtered( + lambda inv: inv.invoice_payment_term_id and 'tempo' in (inv.invoice_payment_term_id.name or '').lower() + ) + _logger.info(f"Invoices tahap 2: {invoices}") + + if not invoices: + _logger.info(f"Tidak ada invoice yang due untuk partner: {partner.name}") + return + + grouped = {} + for inv in invoices: + grouped.setdefault(inv.partner_id, []).append(inv) + + template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') + + for partner, invs in grouped.items(): + # Cari semua kontak anak yang reminder_invoices = True dan punya email + reminder_contacts = self.env['res.partner'].search([ + ('parent_id', '=', partner.id), + ('reminder_invoices', '=', True), + ('email', '!=', False), + ]) + + # Gabungkan partner.email utama + semua email dari child contact reminder + emails = list(filter(None, [partner.email])) + reminder_contacts.mapped('email') + + if not emails: + _logger.info(f"Partner {partner.name} tidak memiliki email yang bisa dikirimi") + continue + + email_to = ",".join(emails) + _logger.info(f"Email tujuan: {email_to}") + + # Buat isi tabel invoice + invoice_table_rows = "" + for inv in invs: + days_to_due = (inv.invoice_date_due - today).days if inv.invoice_date_due else 0 + invoice_table_rows += f""" + + {inv.name} + {fields.Date.to_string(inv.invoice_date) or '-'} + {fields.Date.to_string(inv.invoice_date_due) or '-'} + {days_to_due} + {formatLang(self.env, inv.amount_total, currency_obj=inv.currency_id)} + {inv.ref or '-'} + + """ + + subject = f"Reminder Invoice Due - {partner.name}" + body_html = re.sub( + r"]*>.*?", + f"{invoice_table_rows}", + template.body_html, + flags=re.DOTALL + ).replace('${object.name}', partner.name) \ + .replace('${object.partner_id.name}', partner.name) + + values = { + 'subject': subject, + 'email_to': email_to, + 'email_from': 'finance@indoteknik.co.id', + 'body_html': body_html, + 'reply_to': f'invoice+account.move_{invs[0].id}@indoteknik.co.id', + } + + _logger.info(f"Mengirim email ke: {email_to}") + template.send_mail(invs[0].id, force_send=True, email_values=values) + + # Post ke chatter + user_system = self.env['res.users'].browse(25) + system_id = user_system.partner_id.id if user_system else False + + for inv in invs: + inv.message_post( + subject=subject, + body=body_html, + subtype_id=self.env.ref('mail.mt_note').id, + author_id=system_id, + ) + + _logger.info(f"Reminder terkirim ke {partner.name} ({email_to}) → {len(invs)} invoice") @api.onchange('invoice_date') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index d1d4ca02..f260f58e 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -28,7 +28,7 @@ class ResPartner(models.Model): supplier_ids = fields.Many2many('user.pengajuan.tempo.line', string="Suppliers") reminder_invoices = fields.Boolean( - string='Reminder Invoice', + string='Reminder Invoice?', help='Centang jika kontak ini harus menerima email pengingat invoice.', default=False ) -- cgit v1.2.3 From 827e375b8f20716e08b8e1556961cf243b6a6cb4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 28 Jul 2025 17:58:17 +0700 Subject: push --- 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 79ac3ed9..d75120b7 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -290,9 +290,9 @@ + - Date: Tue, 29 Jul 2025 11:50:51 +0700 Subject: (andri) sales id customer benefits --- indoteknik_custom/models/commision.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 26b5df37..9f7df464 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -215,9 +215,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, default=lambda self: self.env.user, - domain=lambda self: [ - ('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman').id)]) + sales_id = fields.Many2one('res.users', string="Sales", tracking=True, required=True, + domain=[('groups_id', 'in', [94]),('id', '!=', 15710)]) 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 60b037c72887d20e8cebd7f4a2439c546d16960b Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 29 Jul 2025 16:12:42 +0700 Subject: (andri) comment send due inv reminder --- indoteknik_custom/models/account_move.py | 214 +++++++++++++++---------------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 14927bec..945a96f9 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -109,113 +109,113 @@ class AccountMove(models.Model): # result.append((move.id, move.display_name)) # return result - def send_due_invoice_reminder(self): - today = fields.Date.today() - target_dates = [ - today - timedelta(days=7), - today - timedelta(days=3), - today, - today + timedelta(days=3), - today + timedelta(days=7), - ] - - # Contoh khusus partner tertentu - partner = self.env['res.partner'].search([('name', 'ilike', 'BANGUNAN TEKNIK GRUP')], limit=1) - if not partner: - _logger.info("Partner tidak ditemukan.") - return - - invoices = self.env['account.move'].search([ - ('move_type', '=', 'out_invoice'), - ('state', '=', 'posted'), - ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), - ('invoice_date_due', 'in', target_dates), - ('partner_id', '=', partner.id), - ]) - - _logger.info(f"Invoices tahap 1: {invoices}") - - invoices = invoices.filtered( - lambda inv: inv.invoice_payment_term_id and 'tempo' in (inv.invoice_payment_term_id.name or '').lower() - ) - _logger.info(f"Invoices tahap 2: {invoices}") - - if not invoices: - _logger.info(f"Tidak ada invoice yang due untuk partner: {partner.name}") - return - - grouped = {} - for inv in invoices: - grouped.setdefault(inv.partner_id, []).append(inv) - - template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') - - for partner, invs in grouped.items(): - # Cari semua kontak anak yang reminder_invoices = True dan punya email - reminder_contacts = self.env['res.partner'].search([ - ('parent_id', '=', partner.id), - ('reminder_invoices', '=', True), - ('email', '!=', False), - ]) - - # Gabungkan partner.email utama + semua email dari child contact reminder - emails = list(filter(None, [partner.email])) + reminder_contacts.mapped('email') - - if not emails: - _logger.info(f"Partner {partner.name} tidak memiliki email yang bisa dikirimi") - continue - - email_to = ",".join(emails) - _logger.info(f"Email tujuan: {email_to}") - - # Buat isi tabel invoice - invoice_table_rows = "" - for inv in invs: - days_to_due = (inv.invoice_date_due - today).days if inv.invoice_date_due else 0 - invoice_table_rows += f""" - - {inv.name} - {fields.Date.to_string(inv.invoice_date) or '-'} - {fields.Date.to_string(inv.invoice_date_due) or '-'} - {days_to_due} - {formatLang(self.env, inv.amount_total, currency_obj=inv.currency_id)} - {inv.ref or '-'} - - """ - - subject = f"Reminder Invoice Due - {partner.name}" - body_html = re.sub( - r"]*>.*?", - f"{invoice_table_rows}", - template.body_html, - flags=re.DOTALL - ).replace('${object.name}', partner.name) \ - .replace('${object.partner_id.name}', partner.name) - - values = { - 'subject': subject, - 'email_to': email_to, - 'email_from': 'finance@indoteknik.co.id', - 'body_html': body_html, - 'reply_to': f'invoice+account.move_{invs[0].id}@indoteknik.co.id', - } - - _logger.info(f"Mengirim email ke: {email_to}") - template.send_mail(invs[0].id, force_send=True, email_values=values) - - # Post ke chatter - user_system = self.env['res.users'].browse(25) - system_id = user_system.partner_id.id if user_system else False - - for inv in invs: - inv.message_post( - subject=subject, - body=body_html, - subtype_id=self.env.ref('mail.mt_note').id, - author_id=system_id, - ) - - _logger.info(f"Reminder terkirim ke {partner.name} ({email_to}) → {len(invs)} invoice") + # def send_due_invoice_reminder(self): + # today = fields.Date.today() + # target_dates = [ + # today - timedelta(days=7), + # today - timedelta(days=3), + # today, + # today + timedelta(days=3), + # today + timedelta(days=7), + # ] + + # # Contoh khusus partner tertentu + # partner = self.env['res.partner'].search([('name', 'ilike', 'BANGUNAN TEKNIK GRUP')], limit=1) + # if not partner: + # _logger.info("Partner tidak ditemukan.") + # return + + # invoices = self.env['account.move'].search([ + # ('move_type', '=', 'out_invoice'), + # ('state', '=', 'posted'), + # ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), + # ('invoice_date_due', 'in', target_dates), + # ('partner_id', '=', partner.id), + # ]) + + # _logger.info(f"Invoices tahap 1: {invoices}") + + # invoices = invoices.filtered( + # lambda inv: inv.invoice_payment_term_id and 'tempo' in (inv.invoice_payment_term_id.name or '').lower() + # ) + # _logger.info(f"Invoices tahap 2: {invoices}") + + # if not invoices: + # _logger.info(f"Tidak ada invoice yang due untuk partner: {partner.name}") + # return + + # grouped = {} + # for inv in invoices: + # grouped.setdefault(inv.partner_id, []).append(inv) + + # template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') + + # for partner, invs in grouped.items(): + # # Cari semua kontak anak yang reminder_invoices = True dan punya email + # reminder_contacts = self.env['res.partner'].search([ + # ('parent_id', '=', partner.id), + # ('reminder_invoices', '=', True), + # ('email', '!=', False), + # ]) + + # # Gabungkan partner.email utama + semua email dari child contact reminder + # emails = list(filter(None, [partner.email])) + reminder_contacts.mapped('email') + + # if not emails: + # _logger.info(f"Partner {partner.name} tidak memiliki email yang bisa dikirimi") + # continue + + # email_to = ",".join(emails) + # _logger.info(f"Email tujuan: {email_to}") + + # # Buat isi tabel invoice + # invoice_table_rows = "" + # for inv in invs: + # days_to_due = (inv.invoice_date_due - today).days if inv.invoice_date_due else 0 + # invoice_table_rows += f""" + # + # {inv.name} + # {fields.Date.to_string(inv.invoice_date) or '-'} + # {fields.Date.to_string(inv.invoice_date_due) or '-'} + # {days_to_due} + # {formatLang(self.env, inv.amount_total, currency_obj=inv.currency_id)} + # {inv.ref or '-'} + # + # """ + + # subject = f"Reminder Invoice Due - {partner.name}" + # body_html = re.sub( + # r"]*>.*?", + # f"{invoice_table_rows}", + # template.body_html, + # flags=re.DOTALL + # ).replace('${object.name}', partner.name) \ + # .replace('${object.partner_id.name}', partner.name) + + # values = { + # 'subject': subject, + # 'email_to': email_to, + # 'email_from': 'finance@indoteknik.co.id', + # 'body_html': body_html, + # 'reply_to': f'invoice+account.move_{invs[0].id}@indoteknik.co.id', + # } + + # _logger.info(f"Mengirim email ke: {email_to}") + # template.send_mail(invs[0].id, force_send=True, email_values=values) + + # # Post ke chatter + # user_system = self.env['res.users'].browse(25) + # system_id = user_system.partner_id.id if user_system else False + + # for inv in invs: + # inv.message_post( + # subject=subject, + # body=body_html, + # subtype_id=self.env.ref('mail.mt_note').id, + # author_id=system_id, + # ) + + # _logger.info(f"Reminder terkirim ke {partner.name} ({email_to}) → {len(invs)} invoice") @api.onchange('invoice_date') -- cgit v1.2.3 From 55235e9521593311d4e0b4d5dc0e23d7563cf4ac Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 30 Jul 2025 00:35:26 +0700 Subject: rev so done --- indoteknik_custom/models/stock_picking.py | 6 +++ indoteknik_custom/models/tukar_guling.py | 87 +++++++++++++++++++++++-------- indoteknik_custom/views/tukar_guling.xml | 5 +- 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3e152f10..10cf23ef 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1380,6 +1380,12 @@ class StockPicking(models.Model): self.send_mail_bills() if 'BU/PUT' in self.name: self.automatic_reserve_product() + + if self.tukar_guling_id: + self.tukar_guling_id.update_state() + elif self.tukar_guling_po_id: + self.tukar_guling_po_id.update_state() + return res def automatic_reserve_product(self): diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 396e4db4..5411b17c 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -5,7 +5,8 @@ from datetime import datetime _logger = logging.getLogger(__name__) -#TODO + +# TODO # 1. tracking status dokumen BU [X] # 2. ganti nama dokumen # 3. Tracking ketika create dokumen [X] @@ -20,7 +21,7 @@ class TukarGuling(models.Model): _order = 'date desc, id desc' _rec_name = 'name' _inherit = ['mail.thread', 'mail.activity.mixin'] - + partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) origin = fields.Char(string='Origin SO') if_so = fields.Boolean('Is SO', default=True) @@ -62,6 +63,7 @@ class TukarGuling(models.Model): ('approval_sales', ' Approval Sales'), ('approval_finance', 'Approval Finance'), ('approval_logistic', 'Approval Logistic'), + ('approved', 'Waiting for Operations'), ('done', 'Done'), ('cancel', 'Canceled') ], default='draft', tracking=True, required=True) @@ -98,7 +100,7 @@ class TukarGuling(models.Model): @api.onchange('operations') def _onchange_operations(self): """Auto-populate lines ketika operations dipilih""" - if self.operations.picking_type_id.id not in [29,30]: + if self.operations.picking_type_id.id not in [29, 30]: raise UserError("❌ Picking type harus BU/OUT atau BU/PICK") for rec in self: if rec.operations and rec.operations.picking_type_id.id == 30: @@ -217,7 +219,6 @@ class TukarGuling(models.Model): self.origin = False - def action_populate_lines(self): """Manual button untuk populate lines - sebagai alternatif""" self.ensure_one() @@ -257,7 +258,7 @@ class TukarGuling(models.Model): def _check_product_lines(self): """Constraint: Product lines harus ada jika state bukan draft""" for record in self: - if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', + if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', 'approved', 'done') and not record.line_ids: raise ValidationError("Product lines harus diisi sebelum submit atau approve!") @@ -345,7 +346,7 @@ class TukarGuling(models.Model): def write(self, vals): self.ensure_one() - if self.operations.picking_type_id.id not in [29,30]: + if self.operations.picking_type_id.id not in [29, 30]: raise UserError("❌ Picking type harus BU/OUT atau BU/PICK") self._check_invoice_on_revisi_so() operasi = self.operations.picking_type_id.id @@ -376,10 +377,10 @@ class TukarGuling(models.Model): # if self.state == 'done': # raise UserError ("Tidak Boleh delete ketika sudahh done") for record in self: - if record.state == 'done': + if record.state == 'approved' or record.state == 'done': raise UserError( - "Tidak bisa hapus pengajuan jika sudah done, set ke draft terlebih dahulu jika ingin menghapus") - ongoing_bu = self.picking_ids.filtered(lambda p: p.state != 'done') + "Tidak bisa hapus pengajuan jika sudah Approved, set ke draft terlebih dahulu jika ingin menghapus") + ongoing_bu = self.picking_ids.filtered(lambda p: p.state != 'approved') for picking in ongoing_bu: picking.action_cancel() return super(TukarGuling, self).unlink() @@ -461,6 +462,47 @@ class TukarGuling(models.Model): raise UserError("Submit hanya bisa dilakukan dari Draft.") self.state = 'approval_sales' + def update_state(self): + # OUT tukar guling + if self.operations.picking_type_id.id == 29 and self.return_type == 'tukar_guling': + total_out = self.env['stock.picking'].search_count([ + ('tukar_guling_id', '=', self.id), + ('picking_type_id', '=', 29), + ]) + done_out = self.env['stock.picking'].search_count([ + ('tukar_guling_id', '=', self.id), + ('picking_type_id', '=', 29), + ('state', '=', 'done'), + ]) + if self.state == 'approved' and total_out > 0 and done_out == total_out: + self.state = 'done' + + # OUT revisi SO + elif self.operations.picking_type_id.id == 29 and self.return_type == 'revisi_so': + total_ort = self.env['stock.picking'].search_count([ + ('tukar_guling_id', '=', self.id), + ('picking_type_id', '=', 74), + ]) + done_ort = self.env['stock.picking'].search_count([ + ('tukar_guling_id', '=', self.id), + ('picking_type_id', '=', 74), + ('state', '=', 'done'), + ]) + if self.state == 'approved' and total_ort > 0 and done_ort == total_ort: + self.state = 'done' + + # PICK revisi SO + elif self.operations.picking_type_id.id == 30 and self.return_type == 'revisi_so': + done_ort = self.env['stock.picking'].search([ + ('tukar_guling_id', '=', self.id), + ('picking_type_id', '=', 74), + ('state', '=', 'done'), + ]) + if self.state == 'approved' and done_ort: + self.state = 'done' + else: + raise UserError("Tidak bisa menentukan jenis retur.") + def action_approve(self): self.ensure_one() self._validate_product_lines() @@ -510,7 +552,7 @@ class TukarGuling(models.Model): elif rec.state == 'approval_logistic': if not rec.env.user.has_group('indoteknik_custom.group_role_logistic'): raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") - rec.state = 'done' + rec.state = 'approved' rec._create_pickings() rec.date_logistic = now @@ -606,7 +648,8 @@ class TukarGuling(models.Model): ### ======== ORT dari BU/PICK ========= ort_pickings = [] is_retur_from_bu_pick = record.operations.picking_type_id.id == 30 - picks_to_return = [record.operations] if is_retur_from_bu_pick else mapping_koli.mapped('pick_id') or line.product_uom_qty + picks_to_return = [record.operations] if is_retur_from_bu_pick else mapping_koli.mapped( + 'pick_id') or line.product_uom_qty for pick in picks_to_return: ort_return_lines = [] @@ -622,7 +665,8 @@ class TukarGuling(models.Model): 'quantity': line.product_uom_qty, 'move_id': move.id, })) - _logger.info(f"📟 ORT (BU/PICK langsung) | {pick.name} | {line.product_id.display_name} | qty={line.product_uom_qty}") + _logger.info( + f"📟 ORT (BU/PICK langsung) | {pick.name} | {line.product_id.display_name} | qty={line.product_uom_qty}") else: # Ambil dari mapping koli for mk in mapping_koli.filtered(lambda m: m.pick_id == pick): @@ -635,7 +679,8 @@ class TukarGuling(models.Model): 'quantity': mk.qty_return, 'move_id': move.id, })) - _logger.info(f"📟 ORT (mapping koli) | {pick.name} | {mk.product_id.display_name} | qty={mk.qty_return}") + _logger.info( + f"📟 ORT (mapping koli) | {pick.name} | {mk.product_id.display_name} | qty={mk.qty_return}") if ort_return_lines: ort_wizard = self.env['stock.return.picking'].with_context({ @@ -817,18 +862,17 @@ class StockPicking(models.Model): message = _( "📦 %s Validated by %s Status Changed %s at %s." ) % ( - picking.name, - # picking.picking_type_id.name, - picking.env.user.name, - picking.state, - fields.Datetime.now().strftime("%d/%m/%Y %H:%M") - ) + picking.name, + # picking.picking_type_id.name, + picking.env.user.name, + picking.state, + fields.Datetime.now().strftime("%d/%m/%Y %H:%M") + ) picking.tukar_guling_id.message_post(body=message) return res - class TukarGulingMappingKoli(models.Model): _name = 'tukar.guling.mapping.koli' _description = 'Mapping Koli di Tukar Guling' @@ -839,6 +883,7 @@ class TukarGulingMappingKoli(models.Model): qty_done = fields.Float(string='Qty Done BU PICK') qty_return = fields.Float(string='Qty diretur') sequence = fields.Integer(string='Sequence', default=10) + @api.constrains('qty_return') def _check_qty_return_editable(self): for rec in self: @@ -849,4 +894,4 @@ class TukarGulingMappingKoli(models.Model): for rec in self: if rec.tukar_guling_id and rec.tukar_guling_id.state not in ['draft', 'cancel']: raise UserError("Tidak bisa menghapus Mapping Koli karena status Tukar Guling bukan Draft atau Cancel.") - return super(TukarGulingMappingKoli, self).unlink() \ No newline at end of file + return super(TukarGulingMappingKoli, self).unlink() diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index fa3db0d2..c23995d3 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -29,6 +29,7 @@ @@ -58,7 +59,7 @@ class="btn-secondary" attrs="{'invisible': [('state', '!=', 'cancel')]}"/> + statusbar_visible="draft,approval_sales,approval_logistic,approval_finance,approved,done"/>
@@ -66,7 +67,7 @@ type="object" class="oe_stat_button" icon="fa-truck" - attrs="{'invisible': [('picking_ids', '=', False), ('state', 'in', ['draft', 'approval_sales', 'approval_logistic', 'approval_finance'])]}"> + attrs="{'invisible': [('picking_ids', '=', False), ('state', 'in', ['draft', 'approval_sales', 'approval_logistic', 'approval_finance', 'approved', 'done', 'cancel'])]}">
-- cgit v1.2.3 From 6d5435fffa97486b78fd871ed3859acd45830793 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 30 Jul 2025 11:23:02 +0700 Subject: rev po done --- indoteknik_custom/models/tukar_guling_po.py | 43 +++++++++++++++++++++++++++-- indoteknik_custom/views/tukar_guling_po.xml | 3 +- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 11377bc4..23ca1923 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -49,6 +49,7 @@ class TukarGulingPO(models.Model): ('approval_purchase', 'Approval Purchasing'), ('approval_finance', 'Approval Finance'), ('approval_logistic', 'Approval Logistic'), + ('approved', 'Waiting for Operations'), ('done', 'Done'), ('cancel', 'Cancel'), ], string='Status', default='draft', tracking=3) @@ -311,7 +312,7 @@ class TukarGulingPO(models.Model): def unlink(self): for record in self: - if record.state == 'done': + if record.state == 'done' or record.state == 'approved': raise UserError("Tidak bisa hapus pengajuan jika sudah done, set ke draft terlebih dahulu") ongoing_bu = self.po_picking_ids.filtered(lambda p: p.state != 'done') for picking in ongoing_bu: @@ -412,12 +413,50 @@ class TukarGulingPO(models.Model): elif rec.state == 'approval_logistic': if not rec.env.user.has_group('indoteknik_custom.group_role_logistic'): raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") - rec.state = 'done' + rec.state = 'approved' rec._create_pickings() rec.date_logistic = now else: raise UserError("Status ini tidak bisa di-approve.") + def update_stae(self): + # bu input rev po + if self.operations.picking_type_id.id == 28 and self.return_type == 'revisi_po': + prt = self.env['stock.picking'].search([ + ('tukar_guling_po_id', '=', self.id), + ('state', '=', 'done'), + ('picking_type_id.id', '=', 76) + ]) + if self.state == 'aproved' and prt: + self.state = 'done' + # bu put rev po + elif self.operations.picking_type_id.id == 75 and self.return_type == 'revisi_po': + total_prt = self.env['stock.picking'].search_count([ + ('tukar_guling_po_id', '=', self.id), + ('picking_type_id.id', '=', 76) + ]) + prt = self.env['stock.picking'].search_count([ + ('tukar_guling_po_id', '=', self.id), + ('state', '=', 'done'), + ('picking_type_id.id', '=', 76) + ]) + if self.state == 'aproved' and total_prt > 0 and prt == total_prt: + self.state = 'done' + # bu put tukar guling + elif self.operations.picking_type_id.id == 75 and self.return_type == 'tukar_guling': + total_put = self.env['stock.picking'].search_count([ + ('tukar_guling_po_id', '=', self.id), + ('picking_type_id.id', '=', 75) + ]) + put = self.env['stock.picking'].search_count([ + ('tukar_guling_po_id', '=', self.id), + ('state', '=', 'done'), + ('picking_type_id.id', '=', 75) + ]) + if self.state == 'aproved' and total_put > 0 and put == total_put: + self.state = 'done' + + def action_cancel(self): self.ensure_one() # if self.state == 'done': diff --git a/indoteknik_custom/views/tukar_guling_po.xml b/indoteknik_custom/views/tukar_guling_po.xml index 26c0a0d4..accf7dbc 100644 --- a/indoteknik_custom/views/tukar_guling_po.xml +++ b/indoteknik_custom/views/tukar_guling_po.xml @@ -29,6 +29,7 @@ @@ -60,7 +61,7 @@ attrs="{'invisible': [('state', '!=', 'cancel')]}" confirm="Are you sure you want to reset this record to draft?"/> + statusbar_visible="draft,approval_purchase,approval_logistic,approval_finance,approved,done"/>
-- cgit v1.2.3 From d78561e4f4ba8e3a7de58e05fcf8f466c19ec59d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Jul 2025 13:46:54 +0700 Subject: receipt date schema --- indoteknik_custom/models/purchase_order.py | 146 +++++++++++++++++++++---- indoteknik_custom/models/sale_order.py | 7 +- indoteknik_custom/models/stock_picking.py | 4 + indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/purchase_order.xml | 43 +++++++- indoteknik_custom/views/sale_order.xml | 1 + indoteknik_custom/views/stock_picking.xml | 4 + 7 files changed, 180 insertions(+), 26 deletions(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 45134939..27aca0d1 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -92,6 +92,11 @@ class PurchaseOrder(models.Model): is_cab_visible = fields.Boolean(string='Tampilkan Tombol CAB', compute='_compute_is_cab_visible') + reason_change_date_planned = fields.Selection([ + ('delay', 'Delay By Vendor'), + ('urgent', 'Urgent Delivery'), + ], string='Reason Change Date Planned', tracking=True) + # picking_ids = fields.One2many('stock.picking', 'purchase_id', string='Pickings') bu_related_count = fields.Integer( @@ -100,9 +105,68 @@ class PurchaseOrder(models.Model): ) manufacturing_id = fields.Many2one('mrp.production', string='Manufacturing Orders') + complete_bu_in_count = fields.Integer( + string="Complete BU In Count", + compute='_compute_complete_bu_in_count' + ) + + def _compute_complete_bu_in_count(self): + for order in self: + if order.state not in ['done', 'cancel']: + order.complete_bu_in_count = 1 + else: + relevant_pickings = order.picking_ids.filtered( + lambda p: p.state != 'done' + and p.state != 'cancel' + and p.picking_type_code == 'incoming' + and p.origin == order.name + and p.name.startswith('BU/IN') + ) + order.complete_bu_in_count = len(relevant_pickings) + def _has_vcm(self): if self.id: self.vcm_id = self.env['tukar.guling.po'].search([('origin', '=', self.name)], limit=1) + + @api.depends('order_line.date_planned') + def _compute_date_planned(self): + """ date_planned = the earliest date_planned across all order lines. """ + for order in self: + order.date_planned = False + + @api.constrains('date_planned') + def constrains_date_planned(self): + for rec in self: + if not self.env.user.has_group('indoteknik_custom.group_role_purchasing'): + raise ValidationError("Hanya dapat diisi oleh Purchasing") + + base_bu = self.env['stock.picking'].search([ + ('name', 'ilike', 'BU/'), + ('origin', 'ilike', rec.name), + ('group_id', '=', rec.group_id.id), + ('state', 'not in', ['cancel','done']) + ]) + + for bu in base_bu: + bu.write({ + 'scheduled_date': rec.date_planned, + 'reason_change_date_planned': rec.reason_change_date_planned + }) + + rec.sync_date_planned_to_so() + + def sync_date_planned_to_so(self): + for line in self.order_sales_match_line: + other_sales_match = self.env['purchase.order.sales.match'].search([ + # ('product_id', '=', line.product_id.id), + ('sale_id', '=', line.sale_id.id), + # ('sale_line_id', '=', line.sale_line_id.id) + ]) + + dates = [d for d in other_sales_match.mapped('purchase_order_id.date_planned') if d] + if dates: + date_planned = max(dates) + line.sale_id.write({'et_products': date_planned, 'reason_change_date_planned': line.purchase_order_id.reason_change_date_planned}) @api.depends('name') def _compute_bu_related_count(self): @@ -677,13 +741,6 @@ class PurchaseOrder(models.Model): for order in self: order.has_active_invoice = any(invoice.state != 'cancel' for invoice in order.invoice_ids) - # def _compute_has_active_invoice(self): - # for order in self: - # related_invoices = order.invoice_ids.filtered( - # lambda inv: inv.purchase_order_id.id == order.id and inv.move_type == 'in_invoice' and inv.state != 'cancel' - # ) - # order.has_active_invoice = bool(related_invoices) - def add_product_to_pricelist(self): i = 0 for line in self.order_line: @@ -766,16 +823,16 @@ class PurchaseOrder(models.Model): """ purchase_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id) - def _compute_date_planned(self): - for order in self: - if order.date_approve: - leadtime = order.partner_id.leadtime - current_time = order.date_approve - delta_time = current_time + timedelta(days=leadtime) - delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') - order.date_planned = delta_time - else: - order.date_planned = False + # def _compute_date_planned(self): + # for order in self: + # if order.date_approve: + # leadtime = order.partner_id.leadtime + # current_time = order.date_approve + # delta_time = current_time + timedelta(days=leadtime) + # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') + # order.date_planned = delta_time + # else: + # order.date_planned = False def action_create_invoice(self): res = super(PurchaseOrder, self).action_create_invoice() @@ -959,6 +1016,9 @@ class PurchaseOrder(models.Model): if self.amount_untaxed >= 50000000 and not self.env.user.id == 21: raise UserError("Hanya Rafly Hanggara yang bisa approve") + + if not self.date_planned: + raise UserError("Receipt Date harus diisi") if self.total_percent_margin < self.total_so_percent_margin: self.env.user.notify_danger( @@ -975,7 +1035,7 @@ class PurchaseOrder(models.Model): # ) if not self.from_apo: - if (not self.matches_so or not self.sale_order_id) and not self.env.user.is_purchasing_manager and not self.env.user.is_leader and not self.manufacturing_id: + if not self.matches_so and not self.env.user.is_purchasing_manager and not self.env.user.is_leader: raise UserError("Tidak ada link dengan SO, harus di confirm oleh Purchasing Manager") send_email = False @@ -1010,10 +1070,10 @@ class PurchaseOrder(models.Model): self.approve_by = self.env.user.id # override date planned added with two days - leadtime = self.partner_id.leadtime - delta_time = current_time + timedelta(days=leadtime) - delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') - self.date_planned = delta_time + # leadtime = self.partner_id.leadtime + # delta_time = current_time + timedelta(days=leadtime) + # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') + # self.date_planned = delta_time self.date_deadline_ref_date_planned() self.unlink_purchasing_job_state() @@ -1391,6 +1451,20 @@ class PurchaseOrder(models.Model): # Tambahkan pemanggilan method untuk handle pricelist system update self._handle_pricelist_system_update(vals) return res + + def action_open_change_date_wizard(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'res_model': 'change.date.planned.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_purchase_id': self.id, + 'default_new_date_planned': self.date_planned, + } + } + def _handle_pricelist_system_update(self, vals): if 'order_line' in vals or any(key in vals for key in ['state', 'approval_status']): @@ -1479,4 +1553,32 @@ class PurchaseOrderUnlockWizard(models.TransientModel): order.approval_status_unlock = 'pengajuanFinance' return {'type': 'ir.actions.act_window_close'} +class ChangeDatePlannedWizard(models.TransientModel): + _name = 'change.date.planned.wizard' + _description = 'Change Date Planned Wizard' + + purchase_id = fields.Many2one('purchase.order', string="Purchase Order", required=True) + new_date_planned = fields.Datetime(string="New Date Planned") # <- harus DTTM biar match + old_date_planned = fields.Datetime(string="Current Planned Date", related='purchase_id.date_planned', readonly=True) + reason = fields.Selection([ + ('delay', 'Delay By Vendor'), + ('urgent', 'Urgent Delivery'), + ], string='Reason') + date_changed = fields.Boolean(string="Date Changed", compute="_compute_date_changed") + + @api.depends('old_date_planned', 'new_date_planned') + def _compute_date_changed(self): + for rec in self: + rec.date_changed = ( + rec.old_date_planned and rec.new_date_planned and + rec.old_date_planned != rec.new_date_planned + ) + + def confirm_change(self): + self.purchase_id.write({ + 'date_planned': self.new_date_planned, + 'reason_change_date_planned': self.reason, + }) + + diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 7be0e8ff..4e36a9fb 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -350,7 +350,7 @@ class SaleOrder(models.Model): date_unhold = fields.Datetime(string='Date Unhold', tracking=True, readonly=True, help='Waktu ketika SO di Unhold' ) - et_products = fields.Datetime(string='ET Products', compute='_compute_et_products', help="Leadtime produk berdasarkan SLA vendor, tanpa logistik.") + et_products = fields.Datetime(string='ET Products', help="Leadtime produk berdasarkan SLA vendor, tanpa logistik.", tracking=True) eta_date_reserved = fields.Datetime( string="Date Reserved", @@ -381,6 +381,11 @@ class SaleOrder(models.Model): if self.id: self.ccm_id = self.env['tukar.guling'].search([('origin', 'ilike', self.name)], limit=1) + reason_change_date_planned = fields.Selection([ + ('delay', 'Delay By Vendor'), + ('urgent', 'Urgent Delivery'), + ], string='Reason Change Date Planned', tracking=True) + @api.depends('order_line.product_id', 'date_order') def _compute_et_products(self): jakarta = pytz.timezone("Asia/Jakarta") diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3e152f10..5dd5844d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -303,6 +303,10 @@ class StockPicking(models.Model): 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', copy=False) update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') + reason_change_date_planned = fields.Selection([ + ('delay', 'Delay By Vendor'), + ('urgent', 'Urgent Delivery'), + ], string='Reason Change Date Planned', tracking=True) def _get_kgx_awb_number(self): """Menggabungkan name dan origin untuk membuat AWB Number""" diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 015b257c..9b43bf2a 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -161,6 +161,7 @@ access_konfirm_koli,access.konfirm.koli,model_konfirm_koli,,1,1,1,1 access_stock_immediate_transfer,access.stock.immediate.transfer,model_stock_immediate_transfer,,1,1,1,1 access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1 access_purchase_order_unlock_wizard,access.purchase.order.unlock.wizard,model_purchase_order_unlock_wizard,,1,1,1,1 +access_change_date_planned_wizard,access.change.date.planned.wizard,model_change_date_planned_wizard,,1,1,1,1 access_sales_order_koli,access.sales.order.koli,model_sales_order_koli,,1,1,1,1 access_stock_backorder_confirmation,access.stock.backorder.confirmation,model_stock_backorder_confirmation,,1,1,1,1 access_warning_modal_wizard,access.warning.modal.wizard,model_warning_modal_wizard,,1,1,1,1 diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index ff223125..fedcb4f9 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -75,11 +75,13 @@ + + @@ -106,9 +108,16 @@ {'no_create': True} - - 1 - + + + + +