From d2bc3e1d20f196628edaf6956777ca332b8d5063 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 12 Jan 2026 15:02:52 +0700 Subject: create menu monitoring gudang service --- indoteknik_custom/__manifest__.py | 3 +- indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/gudang_service.py | 37 +++++++++++++++ indoteknik_custom/security/ir.model.access.csv | 2 + indoteknik_custom/views/gudang_service.xml | 65 ++++++++++++++++++++++++++ indoteknik_custom/views/ir_sequence.xml | 9 ++++ 6 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 indoteknik_custom/models/gudang_service.py create mode 100644 indoteknik_custom/views/gudang_service.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 66962a24..ddf80cb1 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -192,7 +192,8 @@ 'views/close_tempo_mail_template.xml', 'views/domain_apo.xml', 'views/uom_uom.xml', - 'views/commission_internal.xml' + 'views/commission_internal.xml', + 'views/gudang_service.xml' ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index a14c766e..e6a59246 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -165,3 +165,4 @@ from . import partial_delivery from . import domain_apo from . import uom_uom from . import commission_internal +from . import gudang_service diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py new file mode 100644 index 00000000..a84f5c8a --- /dev/null +++ b/indoteknik_custom/models/gudang_service.py @@ -0,0 +1,37 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError, ValidationError +import logging +from datetime import datetime +from collections import defaultdict + + +class GudangService(models.Model): + _name = "gudang.service" + _description = "Gudang Service" + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char('Name', readonly=True) + partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) + origin = fields.Char(string='Origin SO') + picking_id = fields.Many2one('stock.picking', string = 'Picking ID') + # origin_so = fields.Many2one('sale.order', string='Origin SO', compute='_compute_origin_so') + date = fields.Datetime('Date', default=fields.Datetime.now, required=True) + gudang_service_lines = fields.One2many('gudang.service.line', 'gudang_service_id', string='Gudang Service Lines') + + @api.model + def create(self, vals): + if not vals.get('name') or vals['name'] == 'New': + vals['name'] = self.env['ir.sequence'].next_by_code('gudang.service') + return super(GudangService, self).create(vals) + + +class GudangServiceLine(models.Model): + _name = "gudang.service.line" + _description = "Gudang Service Line" + _inherit = ['mail.thread', 'mail.activity.mixin'] + + product_id = fields.Many2one('product.product', string='Product') + quantity = fields.Float(string='Quantity') + picking_id = fields.Many2one('stock.picking', string = 'Nomor Picking') + # origin_so = fields.Many2one('sale.order', string='Origin SO', compute='_compute_origin_so') + gudang_service_id = fields.Many2one('gudang.service', string='Gudang Service 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 d501de1a..84545488 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -215,3 +215,5 @@ access_surat_piutang_user,surat.piutang user,model_surat_piutang,,1,1,1,1 access_surat_piutang_line_user,surat.piutang.line user,model_surat_piutang_line,,1,1,1,1 access_sj_tele,access.sj.tele,model_sj_tele,base.group_system,1,1,1,1 access_stock_picking_sj_document,stock.picking.sj.document,model_stock_picking_sj_document,base.group_user,1,1,1,1 +access_gudang_service,gudang.service,model_gudang_service,base.group_user,1,1,1,1 +access_gudang_service_line,gudang.service.line,model_gudang_service_line,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml new file mode 100644 index 00000000..19ddcdc6 --- /dev/null +++ b/indoteknik_custom/views/gudang_service.xml @@ -0,0 +1,65 @@ + + + + + + gudang.serivice.tree + gudang.service + + + + + + + + + + + + gudang.service.form + gudang.service + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + +
+
+
+
+ + + Monitoring Gudang Service + ir.actions.act_window + gudang.service + tree,form + + + +
+
diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 46148606..246e3a13 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -260,5 +260,14 @@ 1 1 + + + Gudang Service + gudang.service + MGS/%(year)s/%(month)s/ + 3 + 1 + 1 + \ No newline at end of file -- cgit v1.2.3 From f77738e0c20c01a544dc233c12c1233793b45180 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 14 Jan 2026 17:01:56 +0700 Subject: done temp --- indoteknik_custom/models/gudang_service.py | 93 +++++++++++++++++++++++++++--- indoteknik_custom/views/gudang_service.xml | 35 ++++++++--- indoteknik_custom/views/ir_sequence.xml | 2 +- 3 files changed, 113 insertions(+), 17 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index a84f5c8a..d9b32e91 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -12,26 +12,103 @@ class GudangService(models.Model): name = fields.Char('Name', readonly=True) partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) - origin = fields.Char(string='Origin SO') - picking_id = fields.Many2one('stock.picking', string = 'Picking ID') - # origin_so = fields.Many2one('sale.order', string='Origin SO', compute='_compute_origin_so') + origin = fields.Many2one('sale.order', string='Origin SO') + # picking_id = fields.Many2one('stock.picking', string = 'Picking ID') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) gudang_service_lines = fields.One2many('gudang.service.line', 'gudang_service_id', string='Gudang Service Lines') + remaining_date = fields.Char('Remaining Date', compute='_compute_remaining_date') + state = fields.Selection([('draft', 'Draft'), ('onprogress', 'On Progress'),('done', 'Done'), ('cancel', 'Cancel')], default='draft') + + def _compute_remaining_date(self): + if self.state in ['done', 'cancel', 'draft']: + return + for rec in self: + if rec.date: + rec.remaining_date = (datetime.now() - rec.date).days + + def send_odoo_notification(self): + return { + 'type': 'ir.actions.act_window', + 'res_model': 'mail.message', + 'view_mode': 'form', + 'view_type': 'form', + 'res_id': self.message_ids[-1].id, + } + + def action_submit(self): + self.state = 'onprogress' + # self.send_odoo_notification() + + def action_done(self): + self.state = 'done' + # self.send_odoo_notification() + + def action_draft(self): + """Reset to draft state""" + for record in self: + if record.state == 'cancel': + record.write({'state': 'draft'}) + else: + raise UserError("Hanya record yang di-cancel yang bisa dikembalikan ke draft") + def action_cancel(self): + self.state = 'cancel' + + # def write(vals, self): + # self.send_odoo_notification() + # return super(GudangService, self).write(vals) + + @api.depends('date') + def _compute_remaining_date(self): + for rec in self: + if rec.date: + rec.remaining_date = (datetime.now() - rec.date).days @api.model def create(self, vals): if not vals.get('name') or vals['name'] == 'New': vals['name'] = self.env['ir.sequence'].next_by_code('gudang.service') + + if vals.get('origin') and not vals.get('partner_id'): + so = self.env['sale.order'].browse(vals['origin']) + vals['partner_id'] = so.partner_id.id + return super(GudangService, self).create(vals) + + def write(self, vals): + if vals.get('origin'): + so = self.env['sale.order'].browse(vals['origin']) + vals['partner_id'] = so.partner_id.id + + return super(GudangService, self).write(vals) + + + + @api.onchange('origin') + def _onchange_origin(self): + if not self.origin: + self.gudang_service_lines = [(5, 0, 0)] + return + + self.partner_id = self.origin.partner_id + + lines = [] + for line in self.origin.order_line: + lines.append((0, 0, { + 'product_id': line.product_id.id, + 'quantity': line.product_uom_qty, + 'origin_so': self.origin.id, + })) + + # hapus line lama lalu isi baru + self.gudang_service_lines = [(5, 0, 0)] + lines + class GudangServiceLine(models.Model): _name = "gudang.service.line" - _description = "Gudang Service Line" - _inherit = ['mail.thread', 'mail.activity.mixin'] product_id = fields.Many2one('product.product', string='Product') quantity = fields.Float(string='Quantity') - picking_id = fields.Many2one('stock.picking', string = 'Nomor Picking') - # origin_so = fields.Many2one('sale.order', string='Origin SO', compute='_compute_origin_so') - gudang_service_id = fields.Many2one('gudang.service', string='Gudang Service ID') \ No newline at end of file + # picking_id = fields.Many2one('stock.picking', string='Nomor Picking') + origin_so = fields.Many2one('sale.order', string='Origin SO') + gudang_service_id = fields.Many2one('gudang.service', string='Gudang Service ID') diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index 19ddcdc6..ada1291b 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -10,7 +10,8 @@ - + + @@ -20,16 +21,34 @@ gudang.service
+
+
-
-

- -

-
+
+

+ +

+
- + + + + @@ -37,7 +56,7 @@ - + diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 246e3a13..55e48300 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -265,7 +265,7 @@ Gudang Service gudang.service MGS/%(year)s/%(month)s/ - 3 + 4 1 1 -- cgit v1.2.3 From e1c687df876eaa970cb40393cb4443abcfbd0b77 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 14 Jan 2026 18:55:22 +0700 Subject: add cron notification --- indoteknik_custom/models/gudang_service.py | 50 ++++++++++++++++++++++-------- indoteknik_custom/views/gudang_service.xml | 11 +++++++ 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index d9b32e91..6944f169 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -19,22 +19,45 @@ class GudangService(models.Model): remaining_date = fields.Char('Remaining Date', compute='_compute_remaining_date') state = fields.Selection([('draft', 'Draft'), ('onprogress', 'On Progress'),('done', 'Done'), ('cancel', 'Cancel')], default='draft') - def _compute_remaining_date(self): - if self.state in ['done', 'cancel', 'draft']: + def _send_logistic_notification(self): + logistic_user = self.env.user.has_group('indoteknik_custom.group_role_logistic') + + if not logistic_user: return + for rec in self: - if rec.date: - rec.remaining_date = (datetime.now() - rec.date).days + for user in logistic_user: + self.env['mail.activity'].create({ + 'res_model_id': self.env['ir.model']._get_id('gudang.service'), + 'res_id': rec.id, + 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, + 'user_id': user.id, + 'summary': 'Gudang Service On Progress', + 'note': _( + 'Gudang Service %s masih On Progress sejak %s' + ) % (rec.name, rec.date), + 'date_deadline': fields.Date.today(), + }) + + @api.model + def cron_notify_onprogress_gudang_service(self): + records = self.search([ + ('state', '=', 'onprogress') + ]) + + if records: + records._send_logistic_notification() + + + @api.depends('date', 'state') + def _compute_remaining_date(self): + today = fields.Date.today() + for rec in self: + if rec.state in ['done', 'cancel', 'draft'] or not rec.date: + rec.remaining_date = 0 + continue + rec.remaining_date = (today - rec.date.date()).days - def send_odoo_notification(self): - return { - 'type': 'ir.actions.act_window', - 'res_model': 'mail.message', - 'view_mode': 'form', - 'view_type': 'form', - 'res_id': self.message_ids[-1].id, - } - def action_submit(self): self.state = 'onprogress' # self.send_odoo_notification() @@ -50,6 +73,7 @@ class GudangService(models.Model): record.write({'state': 'draft'}) else: raise UserError("Hanya record yang di-cancel yang bisa dikembalikan ke draft") + def action_cancel(self): self.state = 'cancel' diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index ada1291b..97bb0227 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -81,4 +81,15 @@ action="action_gudang_service" /> + + + Gudang Service Daily Notification + + code + model.cron_notify_onprogress_gudang_service() + 1 + days + -1 + False + -- cgit v1.2.3 From 4e1e2f93b0788c020bd3f1c1f802cf2f53997de5 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 14 Jan 2026 19:19:00 +0700 Subject: fix cron notif & add option to get product from picking --- indoteknik_custom/models/gudang_service.py | 36 +++++++++++++++++++++--------- indoteknik_custom/views/gudang_service.xml | 2 +- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 6944f169..600febfd 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -13,20 +13,41 @@ class GudangService(models.Model): name = fields.Char('Name', readonly=True) partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) origin = fields.Many2one('sale.order', string='Origin SO') - # picking_id = fields.Many2one('stock.picking', string = 'Picking ID') + picking_id = fields.Many2one('stock.picking', string = 'Picking ID', domain="[('sale_id', '=', origin)]") date = fields.Datetime('Date', default=fields.Datetime.now, required=True) gudang_service_lines = fields.One2many('gudang.service.line', 'gudang_service_id', string='Gudang Service Lines') remaining_date = fields.Char('Remaining Date', compute='_compute_remaining_date') state = fields.Selection([('draft', 'Draft'), ('onprogress', 'On Progress'),('done', 'Done'), ('cancel', 'Cancel')], default='draft') + @api.onchange('picking_id') + def _onchange_picking_id(self): + if not self.picking_id: + self.gudang_service_lines = [(5, 0, 0)] + return + + lines = [(5, 0, 0)] + for move in self.picking_id.move_ids_without_package: + if move.product_id: + lines.append((0, 0, { + 'product_id': move.product_id.id, + 'quantity': move.product_uom_qty, + 'origin_so': self.origin.id, + })) + + self.gudang_service_lines = lines + + def _send_logistic_notification(self): - logistic_user = self.env.user.has_group('indoteknik_custom.group_role_logistic') + group = self.env.ref('indoteknik_custom.group_role_logistic', raise_if_not_found=False) + if not group: + return - if not logistic_user: + users = group.users + if not users: return for rec in self: - for user in logistic_user: + for user in users: self.env['mail.activity'].create({ 'res_model_id': self.env['ir.model']._get_id('gudang.service'), 'res_id': rec.id, @@ -60,6 +81,7 @@ class GudangService(models.Model): def action_submit(self): self.state = 'onprogress' + self._send_logistic_notification # self.send_odoo_notification() def action_done(self): @@ -81,12 +103,6 @@ class GudangService(models.Model): # self.send_odoo_notification() # return super(GudangService, self).write(vals) - @api.depends('date') - def _compute_remaining_date(self): - for rec in self: - if rec.date: - rec.remaining_date = (datetime.now() - rec.date).days - @api.model def create(self, vals): if not vals.get('name') or vals['name'] == 'New': diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index 97bb0227..8c681451 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -45,7 +45,7 @@ - + -- cgit v1.2.3 From 2e1e5b798cc5b859a0c10cbdb4e48cbbe0687112 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 15 Jan 2026 00:12:17 +0700 Subject: add cancel reason when trying to cancel and some decoration --- indoteknik_custom/models/gudang_service.py | 39 +++++++++++++++++------------- indoteknik_custom/views/gudang_service.xml | 8 ++++-- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 600febfd..585563ca 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -12,29 +12,30 @@ class GudangService(models.Model): name = fields.Char('Name', readonly=True) partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) - origin = fields.Many2one('sale.order', string='Origin SO') - picking_id = fields.Many2one('stock.picking', string = 'Picking ID', domain="[('sale_id', '=', origin)]") + origin = fields.Many2one('sale.order', string='Origin SO', required=True) + # picking_id = fields.Many2one('stock.picking', string = 'Picking ID', domain="[('sale_id', '=', origin)]") date = fields.Datetime('Date', default=fields.Datetime.now, required=True) gudang_service_lines = fields.One2many('gudang.service.line', 'gudang_service_id', string='Gudang Service Lines') remaining_date = fields.Char('Remaining Date', compute='_compute_remaining_date') state = fields.Selection([('draft', 'Draft'), ('onprogress', 'On Progress'),('done', 'Done'), ('cancel', 'Cancel')], default='draft') + cancel_reason = fields.Text('Cancel Reason') - @api.onchange('picking_id') - def _onchange_picking_id(self): - if not self.picking_id: - self.gudang_service_lines = [(5, 0, 0)] - return + # @api.onchange('picking_id') + # def _onchange_picking_id(self): + # if not self.picking_id: + # self.gudang_service_lines = [(5, 0, 0)] + # return - lines = [(5, 0, 0)] - for move in self.picking_id.move_ids_without_package: - if move.product_id: - lines.append((0, 0, { - 'product_id': move.product_id.id, - 'quantity': move.product_uom_qty, - 'origin_so': self.origin.id, - })) + # lines = [(5, 0, 0)] + # for move in self.picking_id.move_ids_without_package: + # if move.product_id: + # lines.append((0, 0, { + # 'product_id': move.product_id.id, + # 'quantity': move.product_uom_qty, + # 'origin_so': self.origin.id, + # })) - self.gudang_service_lines = lines + # self.gudang_service_lines = lines def _send_logistic_notification(self): @@ -94,9 +95,13 @@ class GudangService(models.Model): if record.state == 'cancel': record.write({'state': 'draft'}) else: - raise UserError("Hanya record yang di-cancel yang bisa dikembalikan ke draft") + raise UserError("Only Canceled Record Can Be Reset To Draft") def action_cancel(self): + if self.state == 'done': + raise UserError("You cannot cancel a done record") + if not self.cancel_reason: + raise UserError("Cancel Reason must be filled") self.state = 'cancel' # def write(vals, self): diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index 8c681451..4365ba5d 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -6,11 +6,13 @@ gudang.serivice.tree gudang.service - + + @@ -45,10 +47,12 @@ - + + -- cgit v1.2.3 From b19788761f065be6593698d07f1e4743e151a8c6 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 15 Jan 2026 00:46:47 +0700 Subject: add done date & code improvement --- indoteknik_custom/models/gudang_service.py | 44 ++++++++++++++++++++---------- indoteknik_custom/views/gudang_service.xml | 14 ++++++++-- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 585563ca..2fe239ad 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -9,12 +9,14 @@ class GudangService(models.Model): _name = "gudang.service" _description = "Gudang Service" _inherit = ['mail.thread', 'mail.activity.mixin'] + _order = 'remaining_date desc, id asc' name = fields.Char('Name', readonly=True) partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) origin = fields.Many2one('sale.order', string='Origin SO', required=True) # picking_id = fields.Many2one('stock.picking', string = 'Picking ID', domain="[('sale_id', '=', origin)]") date = fields.Datetime('Date', default=fields.Datetime.now, required=True) + done_date = fields.Datetime(string='Date Done', copy=False) gudang_service_lines = fields.One2many('gudang.service.line', 'gudang_service_id', string='Gudang Service Lines') remaining_date = fields.Char('Remaining Date', compute='_compute_remaining_date') state = fields.Selection([('draft', 'Draft'), ('onprogress', 'On Progress'),('done', 'Done'), ('cancel', 'Cancel')], default='draft') @@ -71,38 +73,52 @@ class GudangService(models.Model): records._send_logistic_notification() - @api.depends('date', 'state') + @api.depends('date', 'state', 'done_date') def _compute_remaining_date(self): today = fields.Date.today() for rec in self: - if rec.state in ['done', 'cancel', 'draft'] or not rec.date: + if not rec.date: rec.remaining_date = 0 continue + + if rec.state in ['draft', 'cancel']: + rec.remaining_date = 0 + continue + + if rec.state == 'done' and rec.done_date: + rec.remaining_date = (rec.done_date.date() - rec.date.date()).days + continue + rec.remaining_date = (today - rec.date.date()).days + def action_submit(self): - self.state = 'onprogress' + for rec in self: + rec.state = 'onprogress' + rec.date = fields.Datetime.now() self._send_logistic_notification - # self.send_odoo_notification() def action_done(self): - self.state = 'done' - # self.send_odoo_notification() + for rec in self: + rec.state = 'done' + if not rec.done_date: + rec.done_date = fields.Datetime.now() def action_draft(self): """Reset to draft state""" - for record in self: - if record.state == 'cancel': - record.write({'state': 'draft'}) + for rec in self: + if rec.state == 'cancel': + rec.write({'state': 'draft'}) else: raise UserError("Only Canceled Record Can Be Reset To Draft") def action_cancel(self): - if self.state == 'done': - raise UserError("You cannot cancel a done record") - if not self.cancel_reason: - raise UserError("Cancel Reason must be filled") - self.state = 'cancel' + for rec in self: + if rec.state == 'done': + raise UserError("You cannot cancel a done record") + if not rec.cancel_reason: + raise UserError("Cancel Reason must be filled") + rec.state = 'cancel' # def write(vals, self): # self.send_odoo_notification() diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index 4365ba5d..a4150452 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -6,10 +6,19 @@ gudang.serivice.tree gudang.service - + + + @@ -50,6 +59,7 @@ + @@ -59,7 +69,7 @@ - + -- cgit v1.2.3 From 4e372aad3010db13453b9e332c6f058fc8375a99 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 15 Jan 2026 00:52:34 +0700 Subject: add tracking --- indoteknik_custom/models/gudang_service.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 2fe239ad..06d08917 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -9,18 +9,18 @@ class GudangService(models.Model): _name = "gudang.service" _description = "Gudang Service" _inherit = ['mail.thread', 'mail.activity.mixin'] - _order = 'remaining_date desc, id asc' + _order = 'id asc' name = fields.Char('Name', readonly=True) partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) origin = fields.Many2one('sale.order', string='Origin SO', required=True) # picking_id = fields.Many2one('stock.picking', string = 'Picking ID', domain="[('sale_id', '=', origin)]") - date = fields.Datetime('Date', default=fields.Datetime.now, required=True) - done_date = fields.Datetime(string='Date Done', copy=False) + date = fields.Datetime('Date', default=fields.Datetime.now, tracking=True, copy=False) + done_date = fields.Datetime(string='Date Done', copy=False, tracking=True) gudang_service_lines = fields.One2many('gudang.service.line', 'gudang_service_id', string='Gudang Service Lines') remaining_date = fields.Char('Remaining Date', compute='_compute_remaining_date') - state = fields.Selection([('draft', 'Draft'), ('onprogress', 'On Progress'),('done', 'Done'), ('cancel', 'Cancel')], default='draft') - cancel_reason = fields.Text('Cancel Reason') + state = fields.Selection([('draft', 'Draft'), ('onprogress', 'On Progress'),('done', 'Done'), ('cancel', 'Cancel')], default='draft', tracking=True) + cancel_reason = fields.Text('Cancel Reason', tracking=True) # @api.onchange('picking_id') # def _onchange_picking_id(self): -- cgit v1.2.3 From 3b400df6d9f630c4f20e518126afccf280ff62ac Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 15 Jan 2026 09:36:37 +0700 Subject: fix mail activity nyangkut --- indoteknik_custom/models/gudang_service.py | 14 +++++++++++--- indoteknik_custom/views/gudang_service.xml | 10 +++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 06d08917..2a8b589d 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -86,7 +86,8 @@ class GudangService(models.Model): continue if rec.state == 'done' and rec.done_date: - rec.remaining_date = (rec.done_date.date() - rec.date.date()).days + days = (rec.done_date.date() - rec.date.date()).days + rec.remaining_date = "Since %s days" % days continue rec.remaining_date = (today - rec.date.date()).days @@ -96,10 +97,16 @@ class GudangService(models.Model): for rec in self: rec.state = 'onprogress' rec.date = fields.Datetime.now() - self._send_logistic_notification + self._send_logistic_notification() def action_done(self): for rec in self: + activities = self.env['mail.activity'].search([ + ('res_id', '=', rec.id), + ('res_model', '=', 'gudang.service'), + ('state', '=', 'done') + ]) + activities.unlink() rec.state = 'done' if not rec.done_date: rec.done_date = fields.Datetime.now() @@ -167,9 +174,10 @@ class GudangService(models.Model): class GudangServiceLine(models.Model): _name = "gudang.service.line" + _inherit = ['mail.thread', 'mail.activity.mixin'] product_id = fields.Many2one('product.product', string='Product') quantity = fields.Float(string='Quantity') # picking_id = fields.Many2one('stock.picking', string='Nomor Picking') origin_so = fields.Many2one('sale.order', string='Origin SO') - gudang_service_id = fields.Many2one('gudang.service', string='Gudang Service ID') + gudang_service_id = fields.Many2one('gudang.service', string='Gudang Service ID') \ No newline at end of file diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index a4150452..7fc7109b 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -12,7 +12,7 @@ decoration-bf="remaining_date > 7" > --> @@ -20,7 +20,7 @@ - @@ -76,6 +76,10 @@
+
+ + +
@@ -90,7 +94,7 @@ -- cgit v1.2.3 From 6a7c7c1ad694ba12f23b9a4cc5c5deca6ff52bd8 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 15 Jan 2026 13:21:44 +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 2465fa96..c8f7bb5b 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1685,7 +1685,7 @@ class StockPicking(models.Model): ]) for line in po.order_sales_match_line: - if not line.bu_pick: + if not line.bu_pick and line.hold_outgoing_so: continue line.bu_pick.action_assign() -- cgit v1.2.3 From b2cbd45338fd26fb285f68e4c609284395ba9897 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 16 Jan 2026 15:38:15 +0700 Subject: remove from mail activity when docs canceled --- indoteknik_custom/models/gudang_service.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 2a8b589d..c65e599d 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -121,6 +121,11 @@ class GudangService(models.Model): def action_cancel(self): for rec in self: + activities = self.env['mail.activity'].search([ + ('res_id', '=', rec.id), + ('res_model', '=', 'gudang.service'), + ]) + activities.unlink() if rec.state == 'done': raise UserError("You cannot cancel a done record") if not rec.cancel_reason: -- cgit v1.2.3 From 8de3619bc698551e7d23b463d4bcf55f9ad8b62c Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 19 Jan 2026 09:45:14 +0700 Subject: send private message --- indoteknik_custom/models/gudang_service.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 2a8b589d..5aeabb39 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -62,6 +62,11 @@ class GudangService(models.Model): ) % (rec.name, rec.date), 'date_deadline': fields.Date.today(), }) + channel = self.env['mail.channel'].channel_get([self.env.user.partner_id.id, user.partner_id.id]).get('id') + if not channel: + continue + res = self.env['mail.channel'].browse(channel) + res.message_post(body=_('Gudang Service %s masih On Progress sejak %s') % (rec.name, rec.date), message_type='comment', subtype_xmlid='mail.mt_comment') @api.model def cron_notify_onprogress_gudang_service(self): -- cgit v1.2.3 From b9b6d10b518bd9bd80c85216de7ef33cf194f9ba Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 19 Jan 2026 10:37:24 +0700 Subject: add send with user --- indoteknik_custom/models/gudang_service.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 5aeabb39..8edf57d0 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -62,11 +62,13 @@ class GudangService(models.Model): ) % (rec.name, rec.date), 'date_deadline': fields.Date.today(), }) + + # kirim ke private message odoo channel = self.env['mail.channel'].channel_get([self.env.user.partner_id.id, user.partner_id.id]).get('id') if not channel: continue res = self.env['mail.channel'].browse(channel) - res.message_post(body=_('Gudang Service %s masih On Progress sejak %s') % (rec.name, rec.date), message_type='comment', subtype_xmlid='mail.mt_comment') + res.with_user(self.env.user.browse(25)).message_post(body=_('Gudang Service %s masih On Progress sejak %s') % (rec.name, rec.date), message_type='comment', subtype_xmlid='mail.mt_comment') @api.model def cron_notify_onprogress_gudang_service(self): -- cgit v1.2.3 From 9633f55a90d8e70d754e4611c9bfc84d7643f89a Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 19 Jan 2026 11:10:25 +0700 Subject: add MD and exclude some users --- indoteknik_custom/models/gudang_service.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 1aadea70..93f8805a 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -46,11 +46,20 @@ class GudangService(models.Model): return users = group.users + # Safa + md = self.env['res.users'].browse([3425]) + # send to logistic and safa + users = users | md + if not users: return + excluded_users = [7, 17098, 216, 28, 15710] + for rec in self: for user in users: + if user.id in excluded_users: + continue self.env['mail.activity'].create({ 'res_model_id': self.env['ir.model']._get_id('gudang.service'), 'res_id': rec.id, -- cgit v1.2.3 From b40989dc9dc36056f4e416aa5d2e040be595fd61 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 19 Jan 2026 15:22:22 +0700 Subject: done --- indoteknik_custom/models/gudang_service.py | 9 ++++----- indoteknik_custom/views/gudang_service.xml | 15 +++++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 93f8805a..0cdee5af 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -19,7 +19,7 @@ class GudangService(models.Model): done_date = fields.Datetime(string='Date Done', copy=False, tracking=True) gudang_service_lines = fields.One2many('gudang.service.line', 'gudang_service_id', string='Gudang Service Lines') remaining_date = fields.Char('Remaining Date', compute='_compute_remaining_date') - state = fields.Selection([('draft', 'Draft'), ('onprogress', 'On Progress'),('done', 'Done'), ('cancel', 'Cancel')], default='draft', tracking=True) + state = fields.Selection([('draft', 'Backlog'), ('onprogress', 'On Progress'),('done', 'Done'), ('cancel', 'Cancel')], default='draft', tracking=True) cancel_reason = fields.Text('Cancel Reason', tracking=True) # @api.onchange('picking_id') @@ -113,7 +113,6 @@ class GudangService(models.Model): for rec in self: rec.state = 'onprogress' rec.date = fields.Datetime.now() - self._send_logistic_notification() def action_done(self): for rec in self: @@ -130,6 +129,7 @@ class GudangService(models.Model): def action_draft(self): """Reset to draft state""" for rec in self: + rec.cancel_reason = False if rec.state == 'cancel': rec.write({'state': 'draft'}) else: @@ -148,12 +148,11 @@ class GudangService(models.Model): raise UserError("Cancel Reason must be filled") rec.state = 'cancel' - # def write(vals, self): - # self.send_odoo_notification() - # return super(GudangService, self).write(vals) @api.model def create(self, vals): + # Send notification + self._send_logistic_notification() if not vals.get('name') or vals['name'] == 'New': vals['name'] = self.env['ir.sequence'].next_by_code('gudang.service') diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index 7fc7109b..9818825b 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -7,12 +7,12 @@ gudang.service - @@ -20,8 +20,9 @@ - + @@ -85,15 +86,17 @@ - Monitoring Gudang Service + Monitoring Barang Service ir.actions.act_window gudang.service tree,form + {'group_by': ['state']} + Date: Mon, 19 Jan 2026 22:25:00 +0700 Subject: revisi date --- indoteknik_custom/models/gudang_service.py | 55 +++++++++++++++++++----------- indoteknik_custom/views/gudang_service.xml | 31 +++++++++-------- 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 0cdee5af..e401a65c 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -15,7 +15,18 @@ class GudangService(models.Model): partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) origin = fields.Many2one('sale.order', string='Origin SO', required=True) # picking_id = fields.Many2one('stock.picking', string = 'Picking ID', domain="[('sale_id', '=', origin)]") - date = fields.Datetime('Date', default=fields.Datetime.now, tracking=True, copy=False) + schedule_date = fields.Date( + string="Schedule Date", + required=True, + tracking=True + ) + + start_date = fields.Datetime( + string="Date Processed", + copy=False, + tracking=True + ) + done_date = fields.Datetime(string='Date Done', copy=False, tracking=True) gudang_service_lines = fields.One2many('gudang.service.line', 'gudang_service_id', string='Gudang Service Lines') remaining_date = fields.Char('Remaining Date', compute='_compute_remaining_date') @@ -67,9 +78,9 @@ class GudangService(models.Model): 'user_id': user.id, 'summary': 'Gudang Service On Progress', 'note': _( - 'Gudang Service %s masih On Progress sejak %s' - ) % (rec.name, rec.date), - 'date_deadline': fields.Date.today(), + 'Ada Jadwal Service Barang di Document %s Jadwal Service 📅 %s' + ) % (rec.name, rec.schedule_date), + # 'date_deadline': fields.Date.today(), }) # kirim ke private message odoo @@ -77,7 +88,7 @@ class GudangService(models.Model): if not channel: continue res = self.env['mail.channel'].browse(channel) - res.with_user(self.env.user.browse(25)).message_post(body=_('Gudang Service %s masih On Progress sejak %s') % (rec.name, rec.date), message_type='comment', subtype_xmlid='mail.mt_comment') + res.with_user(self.env.user.browse(25)).message_post(body=_('Ada Jadwal Service Barang di Document %s Jadwal Service 📅 %s') % (rec.name, rec.schedule_date), message_type='comment', subtype_xmlid='mail.mt_comment') @api.model def cron_notify_onprogress_gudang_service(self): @@ -89,30 +100,32 @@ class GudangService(models.Model): records._send_logistic_notification() - @api.depends('date', 'state', 'done_date') + @api.depends('start_date', 'done_date', 'state') def _compute_remaining_date(self): today = fields.Date.today() + for rec in self: - if not rec.date: - rec.remaining_date = 0 + if not rec.start_date: + rec.remaining_date = "-" continue - if rec.state in ['draft', 'cancel']: - rec.remaining_date = 0 - continue + start = rec.start_date.date() if rec.state == 'done' and rec.done_date: - days = (rec.done_date.date() - rec.date.date()).days - rec.remaining_date = "Since %s days" % days - continue + end = rec.done_date.date() + else: + end = today + + days = (end - start).days + rec.remaining_date = _("Since %s days") % days - rec.remaining_date = (today - rec.date.date()).days def action_submit(self): for rec in self: rec.state = 'onprogress' - rec.date = fields.Datetime.now() + rec.start_date = fields.Datetime.now() + # rec.date = fields.Datetime.now() def action_done(self): for rec in self: @@ -146,13 +159,14 @@ class GudangService(models.Model): raise UserError("You cannot cancel a done record") if not rec.cancel_reason: raise UserError("Cancel Reason must be filled") + rec.start_date = False + rec.done_date = False rec.state = 'cancel' @api.model def create(self, vals): # Send notification - self._send_logistic_notification() if not vals.get('name') or vals['name'] == 'New': vals['name'] = self.env['ir.sequence'].next_by_code('gudang.service') @@ -160,7 +174,10 @@ class GudangService(models.Model): so = self.env['sale.order'].browse(vals['origin']) vals['partner_id'] = so.partner_id.id - return super(GudangService, self).create(vals) + res = super(GudangService, self).create(vals) + # Send notification + res._send_logistic_notification() + return res def write(self, vals): if vals.get('origin'): @@ -169,8 +186,6 @@ class GudangService(models.Model): return super(GudangService, self).write(vals) - - @api.onchange('origin') def _onchange_origin(self): if not self.origin: diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index 9818825b..42674585 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -18,7 +18,8 @@ - + + @@ -34,7 +35,7 @@
-
-- cgit v1.2.3 From dc2baa117413f3140adf99c8a398da3d7637c3d3 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 10 Feb 2026 18:26:52 +0700 Subject: push --- indoteknik_custom/models/gudang_service.py | 2 +- indoteknik_custom/views/gudang_service.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 4e3b2c34..4900113f 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -157,7 +157,7 @@ class GudangService(models.Model): ('res_model', '=', 'gudang.service'), ]) activities.unlink() - if rec.state == 'done': + if rec.state == 'delivered_to_cust': raise UserError("You cannot cancel a done record") if not rec.cancel_reason: raise UserError("Cancel Reason must be filled") diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index 6abdcc8b..e5cc94c4 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -56,10 +56,10 @@ - + + attrs="{'invisible': [('state', 'in', ['delivered_to_cust', 'draft'])]}"/> -- cgit v1.2.3 From 55a8c1e5b204bd19733f3197b253293bc00fdf32 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 13 Feb 2026 09:42:10 +0700 Subject: fix conflict --- indoteknik_custom/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index b2e94858..6287aace 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -194,7 +194,7 @@ 'views/domain_apo.xml', 'views/uom_uom.xml', 'views/commission_internal.xml', - 'views/gudang_service.xml' + 'views/gudang_service.xml', 'views/update_depreciation_move_wizard_view.xml' ], 'demo': [], -- cgit v1.2.3 From 30e7aae41d50528a4e61e6f55f38e3dd647e41c6 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Sat, 14 Feb 2026 11:14:22 +0700 Subject: merge --- indoteknik_custom/models/__init__.py | 5 +---- indoteknik_custom/security/ir.model.access.csv | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 2b1851ce..3b3dcd9d 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -165,8 +165,5 @@ from . import partial_delivery from . import domain_apo from . import uom_uom from . import commission_internal -<<<<<<< HEAD from . import gudang_service -======= -from . import update_depreciation_move_wizard ->>>>>>> 37e0beac646ee2e676ff935e8289cf3189b3c21b +from . import update_depreciation_move_wizard \ 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 50e2b382..9859b127 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -217,4 +217,4 @@ access_sj_tele,access.sj.tele,model_sj_tele,base.group_system,1,1,1,1 access_stock_picking_sj_document,stock.picking.sj.document,model_stock_picking_sj_document,base.group_user,1,1,1,1 access_gudang_service,gudang.service,model_gudang_service,base.group_user,1,1,1,1 access_gudang_service_line,gudang.service.line,model_gudang_service_line,base.group_user,1,1,1,1 -access_update_depreciation_move_wizard,access.update.depreciation.move.wizard,model_update_depreciation_move_wizard,,1,1,1,1 +access_update_depreciation_move_wizard,access.update.depreciation.move.wizard,model_update_depreciation_move_wizard,,1,1,1,1 \ No newline at end of file -- cgit v1.2.3 From d951091bcb1bcab9d5aedf8060bbc1cd8905326c Mon Sep 17 00:00:00 2001 From: Mqdd Date: Sun, 15 Feb 2026 00:05:12 +0700 Subject: fix state typo & code improvement --- indoteknik_custom/models/gudang_service.py | 65 ++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 4900113f..5d89cfad 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -45,15 +45,49 @@ class GudangService(models.Model): ('cancel', 'Cancel')], default='draft', tracking=True) cancel_reason = fields.Text('Cancel Reason', tracking=True) + @api.constrains('gudang_service_lines') + def _check_qty(self): + for rec in self: + if not rec.origin: + continue + + so_qty_map = { + line.product_id.id: line.product_uom_qty + for line in rec.origin.order_line + } + + for line in rec.gudang_service_lines: + if line.quantity <= 0: + raise ValidationError( + f"Quantity for product {line.product_id.display_name} cannot be 0 or negative." + ) + + so_qty = so_qty_map.get(line.product_id.id, 0) + + if line.quantity > so_qty: + raise ValidationError( + f"Quantity for product {line.product_id.display_name} " + f"cannot exceed SO quantity ({so_qty})." + ) + + + @api.constrains('state', 'schedule_date') + def _check_edit_after_sent(self): + for rec in self: + if rec.state in ['sent_to_vendor', 'received_from_vendor', 'delivered_to_cust']: + if rec._origin.schedule_date != rec.schedule_date: + raise ValidationError("Schedule cannot be modified after sent to vendor") + + def _send_logistic_notification(self): group = self.env.ref('indoteknik_custom.group_role_logistic', raise_if_not_found=False) if not group: return users = group.users - # Safa - md = self.env['res.users'].browse([3425]) - # send to logistic and safa + # MD + md = self.env['res.users'].browse([3425, 4801, 1036]) + # send to logistic and MD users = users | md if not users: @@ -88,7 +122,7 @@ class GudangService(models.Model): @api.model def cron_notify_onprogress_gudang_service(self): records = self.search([ - ('state', 'in', ['draft', 'received_from_customer']), + ('state', 'in', ['draft', 'received_from_cust']), ]) if records: @@ -127,19 +161,16 @@ class GudangService(models.Model): rec.state = 'received_from_vendor' def action_done(self): - if self.state != 'received_from_vendor': - raise UserError("Only 'Received From Vendor' state can be set to Done") - else: - for rec in self: - activities = self.env['mail.activity'].search([ - ('res_id', '=', rec.id), - ('res_model', '=', 'gudang.service'), - ('state', '=', 'delivered_to_cust'), - ]) - activities.unlink() - rec.state = 'delivered_to_cust' - if not rec.done_date: - rec.done_date = fields.Datetime.now() + for rec in self: + if rec.state != 'received_from_vendor': + raise UserError("Only 'Received From Vendor' state can be set to Done") + + rec.activity_ids.unlink() + + rec.write({ + 'state': 'delivered_to_cust', + 'done_date': fields.Datetime.now() + }) def action_draft(self): """Reset to draft state""" -- cgit v1.2.3 From c08d16f53c4e2c97e74f33018e00efabd08664b3 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Sun, 15 Feb 2026 17:36:26 +0700 Subject: push --- indoteknik_custom/views/gudang_service.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml index e5cc94c4..769664c5 100644 --- a/indoteknik_custom/views/gudang_service.xml +++ b/indoteknik_custom/views/gudang_service.xml @@ -54,7 +54,7 @@ - + -- cgit v1.2.3 From 2cf62d4c23e8fbd0770ba05cb4d1f65032bccdf0 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 16 Feb 2026 13:33:02 +0700 Subject: check duplicated mgs docs --- indoteknik_custom/models/gudang_service.py | 239 ++++++++++++++++++----------- 1 file changed, 150 insertions(+), 89 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index 4900113f..b7ccc44f 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -8,51 +8,81 @@ from collections import defaultdict class GudangService(models.Model): _name = "gudang.service" _description = "Gudang Service" - _inherit = ['mail.thread', 'mail.activity.mixin'] - _order = 'id asc' - - name = fields.Char('Name', readonly=True) - partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) - vendor_id = fields.Many2one('res.partner', string='Vendor Service', required=True) - origin = fields.Many2one('sale.order', string='Origin SO', required=True, domain=[('state', 'in', ['done', 'sale'])]) - schedule_date = fields.Date( - string="Schedule Date", + _inherit = ["mail.thread", "mail.activity.mixin"] + _order = "id asc" + + name = fields.Char("Name", readonly=True) + partner_id = fields.Many2one("res.partner", string="Customer", readonly=True) + vendor_id = fields.Many2one("res.partner", string="Vendor Service", required=True) + origin = fields.Many2one( + "sale.order", + string="Origin SO", required=True, - tracking=True + domain=[("state", "in", ["done", "sale"])], ) - start_date = fields.Datetime( - string="Date Processed", - copy=False, - tracking=True + schedule_date = fields.Date(string="Schedule Date", required=True, tracking=True) + start_date = fields.Datetime(string="Date Processed", copy=False, tracking=True) + create_date = fields.Datetime( + string="Create Date", copy=False, tracking=True, default=fields.Datetime.now() + ) + done_date = fields.Datetime(string="Date Done", copy=False, tracking=True) + gudang_service_lines = fields.One2many( + "gudang.service.line", "gudang_service_id", string="Gudang Service Lines" ) - create_date = fields.Datetime(string='Create Date', copy=False, tracking=True, default=fields.Datetime.now()) - done_date = fields.Datetime(string='Date Done', copy=False, tracking=True) - gudang_service_lines = fields.One2many('gudang.service.line', 'gudang_service_id', string='Gudang Service Lines') # unprocessed_date = fields.Char( # string='Unprocessed Since', # compute='_compute_unprocessed_date' # ) remaining_date = fields.Char( - compute='_compute_remaining_date', - string='Date Status' + compute="_compute_remaining_date", string="Date Status" + ) + state = fields.Selection( + [ + ("draft", "Backlog"), + ("received_from_cust", "Received From Customer"), + ("sent_to_vendor", "Sent to Service Vendor"), + ("received_from_vendor", "Received From Service Vendor"), + ("delivered_to_cust", "Delivered to Customer"), + ("cancel", "Cancel"), + ], + default="draft", + tracking=True, ) - state = fields.Selection([ - ('draft', 'Backlog'), - ('received_from_cust', 'Received From Customer'), - ('sent_to_vendor', 'Sent to Service Vendor'), - ('received_from_vendor', 'Received From Service Vendor'), - ('delivered_to_cust', 'Delivered to Customer'), - ('cancel', 'Cancel')], default='draft', tracking=True) - cancel_reason = fields.Text('Cancel Reason', tracking=True) + cancel_reason = fields.Text("Cancel Reason", tracking=True) + + def check_duplicate_docs(self): + for rec in self: + found = self.env["gudang.service"].search( + [ + ("id", "!=", self.id), + ("origin.id", "=", self.origin.id), + ("partner_id.id", "=", rec.partner_id.id), + ( + "gudang_service_lines.product_id.name", + "=", + rec.gudang_service_lines.product_id.name, + ), + ( + "gudang_service_lines.quantity", + "=", + rec.gudang_service_lines.quantity, + ), + ("vendor_id.id", "=", rec.vendor_id.id), + ] + ) + if found: + raise UserError("This Document has duplicate with %s" % found.name) def _send_logistic_notification(self): - group = self.env.ref('indoteknik_custom.group_role_logistic', raise_if_not_found=False) + group = self.env.ref( + "indoteknik_custom.group_role_logistic", raise_if_not_found=False + ) if not group: return users = group.users # Safa - md = self.env['res.users'].browse([3425]) + md = self.env["res.users"].browse([3425]) # send to logistic and safa users = users | md @@ -66,35 +96,53 @@ class GudangService(models.Model): for user in users: if user.id in excluded_users: continue - self.env['mail.activity'].create({ - 'res_model_id': self.env['ir.model']._get_id('gudang.service'), - 'res_id': rec.id, - 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, - 'user_id': user.id, - 'summary': 'Gudang Service On Progress', - 'note': _( - 'Ada Jadwal Service Barang di Document %s Jadwal Service 📅 %s' - ) % (rec.name, rec.schedule_date), - # 'date_deadline': fields.Date.today(), - }) + self.env["mail.activity"].create( + { + "res_model_id": self.env["ir.model"]._get_id("gudang.service"), + "res_id": rec.id, + "activity_type_id": self.env.ref( + "mail.mail_activity_data_todo" + ).id, + "user_id": user.id, + "summary": "Gudang Service On Progress", + "note": _( + "Ada Jadwal Service Barang di Document %s Jadwal Service 📅 %s" + ) + % (rec.name, rec.schedule_date), + # 'date_deadline': fields.Date.today(), + } + ) # kirim ke private message odoo - channel = self.env['mail.channel'].channel_get([self.env.user.partner_id.id, user.partner_id.id]).get('id') + channel = ( + self.env["mail.channel"] + .channel_get([self.env.user.partner_id.id, user.partner_id.id]) + .get("id") + ) if not channel: continue - res = self.env['mail.channel'].browse(channel) - res.with_user(self.env.user.browse(25)).message_post(body=_('Ada Jadwal Service Barang di Document %s Jadwal Service 📅 %s') % (rec.name, rec.schedule_date), message_type='comment', subtype_xmlid='mail.mt_comment') + res = self.env["mail.channel"].browse(channel) + res.with_user(self.env.user.browse(25)).message_post( + body=_( + "Ada Jadwal Service Barang di Document %s Jadwal Service 📅 %s" + ) + % (rec.name, rec.schedule_date), + message_type="comment", + subtype_xmlid="mail.mt_comment", + ) @api.model def cron_notify_onprogress_gudang_service(self): - records = self.search([ - ('state', 'in', ['draft', 'received_from_customer']), - ]) + records = self.search( + [ + ("state", "in", ["draft", "received_from_customer"]), + ] + ) if records: records._send_logistic_notification() - @api.depends('schedule_date', 'create_date') + @api.depends("schedule_date", "create_date") def _compute_remaining_date(self): today = fields.Date.today() @@ -115,29 +163,32 @@ class GudangService(models.Model): else: rec.remaining_date = _("Overdue %s days") % abs(days) - def action_submit(self): + self.ensure_one() + self.check_duplicate_docs() for rec in self: - if rec.state == 'draft': - rec.state = 'received_from_cust' - elif rec.state == 'received_from_cust': - rec.state = 'sent_to_vendor' + if rec.state == "draft": + rec.state = "received_from_cust" + elif rec.state == "received_from_cust": + rec.state = "sent_to_vendor" rec.start_date = fields.Datetime.now() - elif rec.state == 'sent_to_vendor': - rec.state = 'received_from_vendor' + elif rec.state == "sent_to_vendor": + rec.state = "received_from_vendor" def action_done(self): - if self.state != 'received_from_vendor': + if self.state != "received_from_vendor": raise UserError("Only 'Received From Vendor' state can be set to Done") else: for rec in self: - activities = self.env['mail.activity'].search([ - ('res_id', '=', rec.id), - ('res_model', '=', 'gudang.service'), - ('state', '=', 'delivered_to_cust'), - ]) + activities = self.env["mail.activity"].search( + [ + ("res_id", "=", rec.id), + ("res_model", "=", "gudang.service"), + ("state", "=", "delivered_to_cust"), + ] + ) activities.unlink() - rec.state = 'delivered_to_cust' + rec.state = "delivered_to_cust" if not rec.done_date: rec.done_date = fields.Datetime.now() @@ -145,63 +196,73 @@ class GudangService(models.Model): """Reset to draft state""" for rec in self: rec.cancel_reason = False - if rec.state == 'cancel': - rec.write({'state': 'draft'}) + if rec.state == "cancel": + rec.write({"state": "draft"}) else: raise UserError("Only Canceled Record Can Be Reset To Draft") def action_cancel(self): for rec in self: - activities = self.env['mail.activity'].search([ - ('res_id', '=', rec.id), - ('res_model', '=', 'gudang.service'), - ]) + activities = self.env["mail.activity"].search( + [ + ("res_id", "=", rec.id), + ("res_model", "=", "gudang.service"), + ] + ) activities.unlink() - if rec.state == 'delivered_to_cust': + if rec.state == "delivered_to_cust": raise UserError("You cannot cancel a done record") if not rec.cancel_reason: raise UserError("Cancel Reason must be filled") rec.start_date = False rec.done_date = False - rec.state = 'cancel' + rec.state = "cancel" @api.model def create(self, vals): # sequence - if not vals.get('name') or vals['name'] == 'New': - vals['name'] = self.env['ir.sequence'].next_by_code('gudang.service') + if not vals.get("name") or vals["name"] == "New": + vals["name"] = self.env["ir.sequence"].next_by_code("gudang.service") # partner dari SO - so = self.env['sale.order'].browse(vals['origin']) - vals['partner_id'] = so.partner_id.id + so = self.env["sale.order"].browse(vals["origin"]) + vals["partner_id"] = so.partner_id.id res = super(GudangService, self).create(vals) + res.check_duplicate_docs() res._send_logistic_notification() return res - + def write(self, vals): - if vals.get('origin'): - so = self.env['sale.order'].browse(vals['origin']) - vals['partner_id'] = so.partner_id.id + if vals.get("origin"): + so = self.env["sale.order"].browse(vals["origin"]) + vals["partner_id"] = so.partner_id.id + vals.check_duplicate_docs() return super(GudangService, self).write(vals) - @api.onchange('origin') + @api.onchange("origin") def _onchange_origin(self): if not self.origin: self.gudang_service_lines = [(5, 0, 0)] return - + self.partner_id = self.origin.partner_id lines = [] for line in self.origin.order_line: - lines.append((0, 0, { - 'product_id': line.product_id.id, - 'quantity': line.product_uom_qty, - 'origin_so': self.origin.id, - })) + lines.append( + ( + 0, + 0, + { + "product_id": line.product_id.id, + "quantity": line.product_uom_qty, + "origin_so": self.origin.id, + }, + ) + ) # hapus line lama lalu isi baru self.gudang_service_lines = [(5, 0, 0)] + lines @@ -209,9 +270,9 @@ class GudangService(models.Model): class GudangServiceLine(models.Model): _name = "gudang.service.line" - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ["mail.thread", "mail.activity.mixin"] - product_id = fields.Many2one('product.product', string='Product') - quantity = fields.Float(string='Quantity') - origin_so = fields.Many2one('sale.order', string='Origin SO') - gudang_service_id = fields.Many2one('gudang.service', string='Gudang Service ID') \ No newline at end of file + product_id = fields.Many2one("product.product", string="Product") + quantity = fields.Float(string="Quantity") + origin_so = fields.Many2one("sale.order", string="Origin SO") + gudang_service_id = fields.Many2one("gudang.service", string="Gudang Service ID") -- cgit v1.2.3 From 3b0686c3cd83dde114359e5a441d2d7b2c0ebc3f Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 16 Feb 2026 14:39:52 +0700 Subject: add more validation in duplicate docs --- indoteknik_custom/models/gudang_service.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py index bd8ec8bd..d699ccf4 100644 --- a/indoteknik_custom/models/gudang_service.py +++ b/indoteknik_custom/models/gudang_service.py @@ -57,6 +57,8 @@ class GudangService(models.Model): ("id", "!=", self.id), ("origin.id", "=", self.origin.id), ("partner_id.id", "=", rec.partner_id.id), + ("vendor_id.id", "=", rec.vendor_id.id), + ("schedule_date", "=", rec.schedule_date), ( "gudang_service_lines.product_id.name", "=", @@ -67,11 +69,10 @@ class GudangService(models.Model): "=", rec.gudang_service_lines.quantity, ), - ("vendor_id.id", "=", rec.vendor_id.id), ] ) if found: - raise UserError("This Document has duplicate with %s" % found.name) + raise UserError("This Document has duplicate with %s" % found.name) def _send_logistic_notification(self): group = self.env.ref( @@ -82,7 +83,7 @@ class GudangService(models.Model): users = group.users # MD - md = self.env['res.users'].browse([3425, 4801, 1036]) + md = self.env["res.users"].browse([3425, 4801, 1036]) # send to logistic and MD users = users | md @@ -177,15 +178,14 @@ class GudangService(models.Model): def action_done(self): for rec in self: - if rec.state != 'received_from_vendor': + if rec.state != "received_from_vendor": raise UserError("Only 'Received From Vendor' state can be set to Done") rec.activity_ids.unlink() - rec.write({ - 'state': 'delivered_to_cust', - 'done_date': fields.Datetime.now() - }) + rec.write( + {"state": "delivered_to_cust", "done_date": fields.Datetime.now()} + ) def action_draft(self): """Reset to draft state""" -- cgit v1.2.3 From 7456c70b70749acbbe3f6545ee911a754a59f89b Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Thu, 19 Feb 2026 10:28:45 +0700 Subject: (andri) add estimate biaya pum --- .../models/advance_payment_request.py | 34 +++++++++++++++++++++- indoteknik_custom/security/ir.model.access.csv | 1 + .../views/advance_payment_request.xml | 24 +++++++++++++-- .../views/advance_payment_settlement.xml | 11 +++++++ 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/advance_payment_request.py b/indoteknik_custom/models/advance_payment_request.py index 8cadb1b6..18953c7c 100644 --- a/indoteknik_custom/models/advance_payment_request.py +++ b/indoteknik_custom/models/advance_payment_request.py @@ -155,6 +155,16 @@ class AdvancePaymentRequest(models.Model): compute='_compute_is_current_user_ap' ) + estimate_line_ids = fields.One2many('advance.payment.request.estimate.line', 'request_id', string='Rincian Estimasi') + + @api.constrains('nominal', 'estimate_line_ids') + def _check_nominal_vs_estimate_total(self): + for rec in self: + if rec.type_request == 'pum' and rec.estimate_line_ids: + total_estimate = sum(line.nominal for line in rec.estimate_line_ids) + if round(total_estimate, 2) != round(rec.nominal, 2): + raise UserError("Total estimasi harus sama dengan nominal PUM. Silakan sesuaikan rincian estimasi atau nominal PUM.") + @api.onchange('grand_total_reimburse', 'type_request') def _onchange_reimburse_line_update_nominal(self): if self.type_request == 'reimburse': @@ -1144,6 +1154,11 @@ class AdvancePaymentSettlement(models.Model): string="Is Current User AP", compute='_compute_is_current_user_ap' ) + pum_estimate_line_ids = fields.One2many( + related='pum_id.estimate_line_ids', + string='Rincian Estimasi PUM', + readonly=True + ) def _compute_is_current_user_ap(self): ap_user_ids = [23, 9468, 16729] @@ -1612,4 +1627,21 @@ class CreateReimburseCabWizard(models.TransientModel): 'type': 'ir.actions.act_window', 'res_id': move.id, 'target': 'current', - } \ No newline at end of file + } + +class AdvancePaymentRequestEstimateLine(models.Model): + _name = 'advance.payment.request.estimate.line' + _description = 'Advance Payment Request Estimate Line' + + request_id = fields.Many2one('advance.payment.request', string='Request') + category_estimate = fields.Selection([ + ('parkir', 'Parkir'), + ('tol', 'Tol'), + ('bbm', 'BBM'), + ('kuli', 'Kuli'), + ('konsumsi', 'Konsumsi'), + ('lain_lain', 'Lain-lain'), + ], string='Kategori Estimasi', required=True) + description = fields.Text(string='keTambahan', help='Deskripsi tambahan untuk estimasi, misalnya tujuan parkir atau rute tol.') + nominal = fields.Float(string='Nominal Estimasi', required=True, help='Masukkan nominal estimasi untuk kategori ini (tidak mesti akurat, hanya untuk gambaran umum).') + currency_id = fields.Many2one(related='request_id.currency_id') diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index bc290370..1294970c 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -200,6 +200,7 @@ access_advance_payment_usage_line,access.advance.payment.usage.line,model_advanc access_advance_payment_create_bill,access.advance.payment.create.bill,model_advance_payment_create_bill,,1,1,1,1 access_create_reimburse_cab_wizard_user,create.reimburse.cab.wizard user,model_create_reimburse_cab_wizard,,1,1,1,1 access_advance_payment_cancel_wizard,advance.payment.cancel.wizard,model_advance_payment_cancel_wizard,,1,1,1,1 +access_advance_payment_request_estimate_line,advance.payment.request.estimate.line,model_advance_payment_request_estimate_line,,1,1,1,1 access_purchasing_job_seen,purchasing.job.seen,model_purchasing_job_seen,,1,1,1,1 access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1 diff --git a/indoteknik_custom/views/advance_payment_request.xml b/indoteknik_custom/views/advance_payment_request.xml index 340e0caf..4faf905e 100644 --- a/indoteknik_custom/views/advance_payment_request.xml +++ b/indoteknik_custom/views/advance_payment_request.xml @@ -134,8 +134,28 @@
- - + + +

*Masukkan estimasi alokasi biaya sebagai gambaran rencana penggunaan dana, tidak harus diisi dengan nominal yang akurat

+ + + + + + + + + + + + + + + + + +
+ diff --git a/indoteknik_custom/views/advance_payment_settlement.xml b/indoteknik_custom/views/advance_payment_settlement.xml index a8bf1de7..258f469f 100644 --- a/indoteknik_custom/views/advance_payment_settlement.xml +++ b/indoteknik_custom/views/advance_payment_settlement.xml @@ -124,6 +124,17 @@ + +

*Rincian estimasi PUM ini hanya sebagai gambaran umum untuk realisasi yang dilakukan, tidak harus diisi dengan nominal yang akurat.

+ + + + + + + + +
-- cgit v1.2.3 From 10525d407bbc34d79146ff22955a874509f5b204 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Thu, 19 Feb 2026 15:55:27 +0700 Subject: (andri) compute estimate dengan realisasi --- .../models/advance_payment_request.py | 60 +++++++++++++++++++++- .../views/advance_payment_settlement.xml | 3 ++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/advance_payment_request.py b/indoteknik_custom/models/advance_payment_request.py index 18953c7c..ac9d7c2d 100644 --- a/indoteknik_custom/models/advance_payment_request.py +++ b/indoteknik_custom/models/advance_payment_request.py @@ -921,6 +921,35 @@ class AdvancePaymentUsageLine(models.Model): compute='_compute_is_current_user_ap' ) + category_usage = fields.Selection([ + ('parkir', 'Parkir'), + ('tol', 'Tol'), + ('bbm', 'BBM'), + ('kuli', 'Kuli'), + ('konsumsi', 'Konsumsi'), + ('lain_lain', 'Lain-lain'), + ], string='Kategori System', compute='_compute_category_usage') + + @api.depends('account_id') + def _compute_category_usage(self): + for rec in self: + if not rec.account_id: + rec.category_usage = False + continue + name = rec.account_id.name.lower() + if 'bbm' in name or 'bahan bakar' in name: + rec.category_usage = 'bbm' + elif 'tol' in name: + rec.category_usage = 'tol' + elif 'parkir' in name: + rec.category_usage = 'parkir' + elif 'kuli' in name or 'bongkar' in name: + rec.category_usage = 'kuli' + elif 'konsumsi' in name or 'makan' in name or 'minum' in name: + rec.category_usage = 'konsumsi' + else: + rec.category_usage = 'lain_lain' + def _compute_is_current_user_ap(self): ap_user_ids = [23, 9468, 16729] is_ap = self.env.user.id in ap_user_ids @@ -1642,6 +1671,35 @@ class AdvancePaymentRequestEstimateLine(models.Model): ('konsumsi', 'Konsumsi'), ('lain_lain', 'Lain-lain'), ], string='Kategori Estimasi', required=True) - description = fields.Text(string='keTambahan', help='Deskripsi tambahan untuk estimasi, misalnya tujuan parkir atau rute tol.') + description = fields.Text(string='Description', help='Deskripsi tambahan untuk estimasi biaya yang diperlukan.') nominal = fields.Float(string='Nominal Estimasi', required=True, help='Masukkan nominal estimasi untuk kategori ini (tidak mesti akurat, hanya untuk gambaran umum).') currency_id = fields.Many2one(related='request_id.currency_id') + + total_actual = fields.Float(string='Nominal Realisasi', compute='_compute_actual_data') + frequency = fields.Integer(string='Qty Realisasi', compute='_compute_actual_data') + + @api.depends('request_id.settlement_ids.penggunaan_line_ids.nominal', + 'request_id.settlement_ids.penggunaan_line_ids.account_id') + def _compute_actual_data(self): + for rec in self: + total_act = 0 + freq = 0 + if rec.request_id and rec.request_id.settlement_ids: + all_usage_lines = rec.request_id.settlement_ids.mapped('penggunaan_line_ids') + valid_lines = all_usage_lines.filtered(lambda l: l.account_id and l.category_usage) + planned_category = rec.request_id.estimate_line_ids.mapped('category_estimate') + if rec.category_estimate == 'lain_lain': + matched_lines = valid_lines.filtered( + lambda l: l.category_usage == 'lain_lain' or \ + l.category_usage not in planned_category + ) + else: + matched_lines = valid_lines.filtered( + lambda l: l.category_usage == rec.category_estimate + ) + + total_act = sum(matched_lines.mapped('nominal')) + freq = len(matched_lines) + rec.total_actual = total_act + rec.frequency = freq + diff --git a/indoteknik_custom/views/advance_payment_settlement.xml b/indoteknik_custom/views/advance_payment_settlement.xml index 258f469f..352c5b96 100644 --- a/indoteknik_custom/views/advance_payment_settlement.xml +++ b/indoteknik_custom/views/advance_payment_settlement.xml @@ -118,6 +118,7 @@ + @@ -131,6 +132,8 @@ + + -- cgit v1.2.3 From 7394cd71c05d56ebfd5a5f95518a849289a782e0 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 20 Feb 2026 10:17:13 +0700 Subject: fix not sync to solr when there is no products --- indoteknik_custom/models/keywords.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 4ab649dc..9c8e2cfd 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -133,7 +133,7 @@ class Keywords(models.Model): def _onchange_solr_flag(self): """Set solr_flag=2 when tracked fields change to trigger queue sync""" for record in self: - if not record.skip: + if not record.skip and len(record.product_ids) > 0: record.solr_flag = 2 def solr_flag_to_queue(self, limit=500): @@ -164,6 +164,11 @@ class Keywords(models.Model): """Callback method executed by apache.solr.queue - syncs keyword data to Solr""" documents = [] for keyword in self: + # Skip syncing if product count is 0 + if len(keyword.product_ids) == 0: + _logger.info('Skipping Solr sync for keyword "%s" - no products found', keyword.keywords) + continue + searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') try: doc = { @@ -194,6 +199,11 @@ class Keywords(models.Model): documents = [] for keyword in keywords: + # Skip syncing if product count is 0 + if len(keyword.product_ids) == 0: + _logger.info('Skipping Solr sync for keyword "%s" - no products found', keyword.keywords) + continue + searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') try: doc = { @@ -227,6 +237,8 @@ class Keywords(models.Model): neded_sync = any(field in vals for field in tracked_fields) if neded_sync and self.skip == False: for record in self: - record.solr_flag = 2 + # Only flag for sync if there are products + if len(record.product_ids) > 0: + record.solr_flag = 2 return result -- cgit v1.2.3 From 62c366e5cf58567b61700d2922b864e348e2a44b Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 20 Feb 2026 11:14:23 +0700 Subject: remove add to keywords when creating a product --- indoteknik_custom/models/product_template.py | 31 +--------------------------- 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 397bd06d..e10b4de2 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -94,7 +94,6 @@ class ProductTemplate(models.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) - self.env['product.product']._add_product_to_keywords(result) return result # def write(self, values): @@ -895,11 +894,6 @@ class ProductTemplate(models.Model): # Log changes self._log_field_changes_product(vals, old_values) - # Add product to keywords - keyword_trigger = ['name', 'website_description', 'unpublished'] - if any(field in vals for field in keyword_trigger): - for product in self: - self.env['product.product']._add_product_to_keywords(product) return result # def write(self, vals): @@ -946,26 +940,7 @@ class ProductProduct(models.Model): 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") - keyword_id = fields.Many2one('keywords', string='Keyword') - - def _add_product_to_keywords(self,product): - keywords_model = self.env['keywords'] - if not product: - return False - - for product in self: - match_keywords = keywords_model.search([ - '|', - ('name', 'ilike', product.name), - ('keywords', 'ilike', product.website_description) - ]) - - for kw in match_keywords.filtered(lambda k: not k.skip): - if not self.unpublished and product.id not in kw.product_ids.ids: - kw.write({'product_ids': [(4, product.id)]}) - - return True - + # keyword_id = fields.Many2one('keywords', string='Keyword') has_magento = fields.Boolean(string='Has Magento?', default=False, readonly=True) def generate_product_sla(self): @@ -988,7 +963,6 @@ class ProductProduct(models.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(ProductProduct, self).create(vals) - self._add_product_to_keywords(result) return result # def write(self, values): @@ -1355,9 +1329,6 @@ class ProductProduct(models.Model): ] # pake ini kalau mau Cek semua field # if vals: - trigger_fields = ['name', 'website_description', 'unpublished'] - if any(f in vals for f in trigger_fields): - self._add_product_to_keywords(vals) if any(field in vals for field in tracked_fields): old_values = self._collect_old_values(vals) result = super().write(vals) -- cgit v1.2.3 From a3dc6bc79215e6223c801f49d1997292b8f4c317 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Fri, 20 Feb 2026 15:36:56 +0700 Subject: (andri) fix --- indoteknik_custom/models/advance_payment_request.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/advance_payment_request.py b/indoteknik_custom/models/advance_payment_request.py index ac9d7c2d..039d18a5 100644 --- a/indoteknik_custom/models/advance_payment_request.py +++ b/indoteknik_custom/models/advance_payment_request.py @@ -160,7 +160,9 @@ class AdvancePaymentRequest(models.Model): @api.constrains('nominal', 'estimate_line_ids') def _check_nominal_vs_estimate_total(self): for rec in self: - if rec.type_request == 'pum' and rec.estimate_line_ids: + if rec.type_request == 'pum': + if not rec.estimate_line_ids: + raise UserError("Rincian estimasi wajib diisi untuk PUM. Silakan tambahkan rincian estimasi.") total_estimate = sum(line.nominal for line in rec.estimate_line_ids) if round(total_estimate, 2) != round(rec.nominal, 2): raise UserError("Total estimasi harus sama dengan nominal PUM. Silakan sesuaikan rincian estimasi atau nominal PUM.") @@ -761,7 +763,7 @@ class AdvancePaymentRequest(models.Model): pum_ids = self.search([ ('user_id', '=', user.id), - ('status', '!=', 'reject'), + ('status', '!=', 'cancel'), ('type_request', '=', 'pum') ]) -- cgit v1.2.3 From 52e196b5aceb5b2cd91795a08f4be1517b123583 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Fri, 20 Feb 2026 15:57:34 +0700 Subject: hide pos --- 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 90cd5fa2..e6fc4732 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -647,7 +647,7 @@ class SaleOrder(models.Model): def _get_biteship_courier_codes(self): return [ - 'gojek','grab','deliveree','lalamove','jne','ninja','lion','rara','sicepat','jnt','pos','idexpress','rpx','wahana','jdl','anteraja','sap','paxel','borzo' + 'gojek','grab','deliveree','lalamove','jne','ninja','lion','rara','sicepat','jnt','idexpress','rpx','wahana','jdl','anteraja','sap','paxel','borzo' ] @api.onchange('carrier_id') -- cgit v1.2.3 From 2c89059ab4abc70f6e7f811b7995bc394d6514f8 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 20 Feb 2026 16:21:28 +0700 Subject: fix price not same as calculated on excel --- indoteknik_custom/models/price_group.py | 20 ++++++++++---------- indoteknik_custom/models/purchase_pricelist.py | 7 ++++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/indoteknik_custom/models/price_group.py b/indoteknik_custom/models/price_group.py index fce78fff..1a4f1bd6 100644 --- a/indoteknik_custom/models/price_group.py +++ b/indoteknik_custom/models/price_group.py @@ -10,16 +10,16 @@ class PriceGroup(models.Model): name = fields.Char(string='Name') pricelist_id = fields.Many2one('product.pricelist', string='Price List') - group1 = fields.Float(string='Kelompok 1 (%)') - group2 = fields.Float(string='Kelompok 2 (%)') - group3 = fields.Float(string='Kelompok 3 (%)') - group4 = fields.Float(string='Kelompok 4 (%)') - group5 = fields.Float(string='Kelompok 5 (%)') - group6 = fields.Float(string='Kelompok 6 (%)') - group7 = fields.Float(string='Kelompok 7 (%)') - group8 = fields.Float(string='Kelompok 8 (%)') - group9 = fields.Float(string='Kelompok 9 (%)') - group10 = fields.Float(string='Kelompok 10 (%)') + group1 = fields.Float(string='Kelompok 1 (%)', digits=(16, 12)) + group2 = fields.Float(string='Kelompok 2 (%)', digits=(16, 12)) + group3 = fields.Float(string='Kelompok 3 (%)', digits=(16, 12)) + group4 = fields.Float(string='Kelompok 4 (%)', digits=(16, 12)) + group5 = fields.Float(string='Kelompok 5 (%)', digits=(16, 12)) + group6 = fields.Float(string='Kelompok 6 (%)', digits=(16, 12)) + group7 = fields.Float(string='Kelompok 7 (%)', digits=(16, 12)) + group8 = fields.Float(string='Kelompok 8 (%)', digits=(16, 12)) + group9 = fields.Float(string='Kelompok 9 (%)', digits=(16, 12)) + group10 = fields.Float(string='Kelompok 10 (%)', digits=(16, 12)) def collect_price_group(self): PRICE_GROUP_ID = { diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py index b3a473b6..83e06f55 100755 --- a/indoteknik_custom/models/purchase_pricelist.py +++ b/indoteknik_custom/models/purchase_pricelist.py @@ -118,15 +118,16 @@ class PurchasePricelist(models.Model): product_domain = [('product_id', '=', rec.product_id.id)] markup_pricelist = price_group['markup'].pricelist_id - base_price = price_incl + (price_incl * markup_percentage / 100) + # base_price = price_incl + (price_incl * markup_percentage / 100) + base_price = round(price_incl + (price_incl * markup_percentage / 100), 12) base_prod_pricelist = self.env['product.pricelist.item'].search(product_domain + [('pricelist_id', '=', markup_pricelist.id)], limit=1) base_prod_pricelist.fixed_price = base_price tier_percentages = [price_group[f'tier_{i}'][product_group] for i in range(1, 6)] for i, tier_percentage in enumerate(tier_percentages): tier_pricelist = price_group[f'tier_{i + 1}'].pricelist_id - tier_price = price_incl + (price_incl * tier_percentage / 100) - tier_perc = (base_price - tier_price) / base_price * 100 + tier_price = round(price_incl + (price_incl * tier_percentage / 100), 12) + tier_perc = round((base_price - tier_price) / base_price * 100, 12) tier_prod_pricelist = self.env['product.pricelist.item'].search(product_domain + [('pricelist_id', '=', tier_pricelist.id)], limit=1) tier_prod_pricelist.price_discount = tier_perc -- cgit v1.2.3 From abccff65f5eb91eafe08025ebc86d5e220243854 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Sat, 21 Feb 2026 08:15:45 +0700 Subject: fix logic sync solr --- indoteknik_custom/models/keywords.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 9c8e2cfd..487e58e5 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -133,7 +133,7 @@ class Keywords(models.Model): def _onchange_solr_flag(self): """Set solr_flag=2 when tracked fields change to trigger queue sync""" for record in self: - if not record.skip and len(record.product_ids) > 0: + if len(record.product_ids) > 0: record.solr_flag = 2 def solr_flag_to_queue(self, limit=500): @@ -235,7 +235,7 @@ class Keywords(models.Model): tracked_fields = ['keywords', 'category_id', 'product_ids'] neded_sync = any(field in vals for field in tracked_fields) - if neded_sync and self.skip == False: + if neded_sync: for record in self: # Only flag for sync if there are products if len(record.product_ids) > 0: -- cgit v1.2.3 From 345d45f5fae0f152ca39e9ba491513582a5c6791 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Sat, 21 Feb 2026 09:41:57 +0700 Subject: fix solr queue keywords --- indoteknik_custom/models/keywords.py | 64 +++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 487e58e5..2ee217f7 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -160,16 +160,50 @@ class Keywords(models.Model): return True + # Old + # def _sync_keywords_queue_callback(self): + # """Callback method executed by apache.solr.queue - syncs keyword data to Solr""" + # documents = [] + # for keyword in self: + # # Skip syncing if product count is 0 + # if len(keyword.product_ids) == 0: + # _logger.info('Skipping Solr sync for keyword "%s" - no products found', keyword.keywords) + # continue + + # searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') + # try: + # doc = { + # 'id': keyword.id, + # 'category_id_i': keyword.category_id.id, + # 'keywords_s': searchkey, + # 'url_s': keyword.url, + # 'product_ids_is': [p.product_tmpl_id.id for p in keyword.product_ids], + # } + # documents.append(doc) + # except Exception as e: + # _logger.error('failed %s', e) + # _logger.error('doc data: %s', doc) + + # if documents: + # solr.add(documents) + + # self.write({'solr_flag': 0}) + + # return True + def _sync_keywords_queue_callback(self): - """Callback method executed by apache.solr.queue - syncs keyword data to Solr""" - documents = [] + success_keywords = self.browse() + for keyword in self: - # Skip syncing if product count is 0 - if len(keyword.product_ids) == 0: - _logger.info('Skipping Solr sync for keyword "%s" - no products found', keyword.keywords) + if not keyword.product_ids: + _logger.info( + 'Skipping Solr sync for keyword "%s" - no products found', + keyword.keywords + ) continue - + searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') + try: doc = { 'id': keyword.id, @@ -178,13 +212,19 @@ class Keywords(models.Model): 'url_s': keyword.url, 'product_ids_is': [p.product_tmpl_id.id for p in keyword.product_ids], } - documents.append(doc) + + solr.add([doc]) + + success_keywords |= keyword + except Exception as e: - _logger.error('failed %s', e) - _logger.error('doc data: %s', doc) + _logger.error( + "Solr sync failed for keyword ID %s: %s", + keyword.id, e + ) - if documents: - solr.add(documents) + if success_keywords: + success_keywords.write({'solr_flag': 0}) return True @@ -233,7 +273,7 @@ class Keywords(models.Model): def write(self, vals): result = super().write(vals) - tracked_fields = ['keywords', 'category_id', 'product_ids'] + tracked_fields = ['keywords', 'category_id', 'product_ids', 'skip', 'name'] neded_sync = any(field in vals for field in tracked_fields) if neded_sync: for record in self: -- cgit v1.2.3 From 18bdbf118d2f582ac0c520a9b4cc5b61fe88488f Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 23 Feb 2026 10:39:48 +0700 Subject: show date maturity in line & remove old keywords code --- indoteknik_custom/models/keywords.py | 31 --------------------------- indoteknik_custom/views/account_move_line.xml | 3 +++ 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 2ee217f7..3fa9dd72 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -160,37 +160,6 @@ class Keywords(models.Model): return True - # Old - # def _sync_keywords_queue_callback(self): - # """Callback method executed by apache.solr.queue - syncs keyword data to Solr""" - # documents = [] - # for keyword in self: - # # Skip syncing if product count is 0 - # if len(keyword.product_ids) == 0: - # _logger.info('Skipping Solr sync for keyword "%s" - no products found', keyword.keywords) - # continue - - # searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') - # try: - # doc = { - # 'id': keyword.id, - # 'category_id_i': keyword.category_id.id, - # 'keywords_s': searchkey, - # 'url_s': keyword.url, - # 'product_ids_is': [p.product_tmpl_id.id for p in keyword.product_ids], - # } - # documents.append(doc) - # except Exception as e: - # _logger.error('failed %s', e) - # _logger.error('doc data: %s', doc) - - # if documents: - # solr.add(documents) - - # self.write({'solr_flag': 0}) - - # return True - def _sync_keywords_queue_callback(self): success_keywords = self.browse() diff --git a/indoteknik_custom/views/account_move_line.xml b/indoteknik_custom/views/account_move_line.xml index 838596c8..346494f3 100644 --- a/indoteknik_custom/views/account_move_line.xml +++ b/indoteknik_custom/views/account_move_line.xml @@ -9,6 +9,9 @@ + + + -- cgit v1.2.3 From 79471b69ffc683c3c860ecd6456f638750a8c81f Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 24 Feb 2026 09:56:19 +0700 Subject: kartu stock --- indoteknik_custom/__manifest__.py | 5 +- indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/kartu_stock.py | 186 ++++++++++++++++++++++++++++++++ indoteknik_custom/models/keywords.py | 2 +- indoteknik_custom/views/kartu_stock.xml | 17 +++ 5 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 indoteknik_custom/models/kartu_stock.py create mode 100644 indoteknik_custom/views/kartu_stock.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 36588967..3e39942b 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -8,7 +8,7 @@ 'author': 'Rafi Zadanly', 'website': '', 'images': ['assets/favicon.ico'], - 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur'], + 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur', 'proweb_kartu_stok'], 'data': [ 'views/assets.xml', 'security/ir.model.access.csv', @@ -191,7 +191,8 @@ 'views/uom_uom.xml', 'views/update_depreciation_move_wizard_view.xml', 'views/commission_internal.xml', - 'views/keywords.xml' + 'views/keywords.xml', + 'views/kartu_stock.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index a042750b..840796f8 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -167,3 +167,4 @@ from . import uom_uom from . import commission_internal from . import update_depreciation_move_wizard from . import keywords +from . import kartu_stock diff --git a/indoteknik_custom/models/kartu_stock.py b/indoteknik_custom/models/kartu_stock.py new file mode 100644 index 00000000..22f90df0 --- /dev/null +++ b/indoteknik_custom/models/kartu_stock.py @@ -0,0 +1,186 @@ +from odoo import models +from io import BytesIO +import datetime +from base64 import encodebytes +import xlsxwriter + + +class KartuStokWizardInherit(models.TransientModel): + _inherit = 'kartu.stok.wizard' + + + def action_kartu_stok_excel_single_sheet(self): + + active_ids_tmp = self.env.context.get('active_ids') + active_model = self.env.context.get('active_model') + + if active_model == 'product.template': + active_ids = self.env['product.product'].search( + [('product_tmpl_id', 'in', active_ids_tmp), + ('active', '=', True)]).ids + else: + active_ids = active_ids_tmp + + data = { + 'location_id': self.location_id.id, + 'day_date': self.day_date, + 'previous_number_days': self.previous_number_days, + 'date_from': self.date_from, + 'date_to': self.date_to, + 'ids': active_ids, + 'context': {'active_model': active_model} + } + + file_io = BytesIO() + workbook = xlsxwriter.Workbook(file_io) + + self.generate_xlsx_single_sheet(workbook, data) + + workbook.close() + + fout = encodebytes(file_io.getvalue()) + datetime_string = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f'Kartu_Stok_Single_{datetime_string}.xlsx' + + self.write({ + 'fileout': fout, + 'fileout_filename': filename + }) + + file_io.close() + + return { + 'type': 'ir.actions.act_url', + 'target': 'new', + 'url': 'web/content/?model=' + self._name + + '&id=' + str(self.id) + + '&field=fileout&download=true&filename=' + filename, + } + + def generate_xlsx_single_sheet(self, workbook, data): + + bold = workbook.add_format({'bold': True}) + border_date_right = workbook.add_format({'border':1, 'num_format': 'DD-MM', 'bg_color': '#dddddd', 'align': 'right'}) + border_int_right = workbook.add_format({'border':1, 'num_format': '#,##0', 'bg_color': '#dddddd', 'align': 'right'}) + border_int_right_bold = workbook.add_format({'border':1, 'num_format': '#,##0', 'bg_color': '#dddddd', 'align': 'right', 'bold': True}) + border_text_center = workbook.add_format({'border':1, 'bg_color': '#dddddd', 'align': 'center'}) + header_format = workbook.add_format({'bold': True, 'border':1, 'bg_color': '#808080', 'align': 'center'}) + + sheet = workbook.add_worksheet('Kartu Stok') + + docs = self.env['product.product'].browse(data['ids']) + location = self.env['stock.location'].browse(data['location_id']) + location_name = location.display_name.split('/')[0] + + row = 0 + + for doc in docs: + + # ========================= + # HEADER PRODUCT + # ========================= + sheet.write(row, 0, doc.display_name, bold) + row += 1 + sheet.write(row, 0, location_name, bold) + row += 2 + + # ========================= + # TABLE HEADER + # ========================= + sheet.write(row, 0, 'Date', header_format) + sheet.write(row, 1, 'In', header_format) + sheet.write(row, 2, 'Out', header_format) + sheet.write(row, 3, 'Stock', header_format) + sheet.write(row, 4, 'Distributor', header_format) + sheet.write(row, 5, 'Buyer', header_format) + sheet.write(row, 6, 'Document', header_format) + sheet.write(row, 7, 'Source Document', header_format) + row += 1 + + stock_total = 0 + stock_show_initial = False + + # ========================= + # MOVE LOOP (SAMA LOGIC ASLI) + # ========================= + for move in doc.stock_move_ids.sorted(key=lambda sm: sm.date): + for line in move.move_line_ids: + + if line.state != 'done': + continue + + if line.location_id.id != data['location_id'] and \ + line.location_dest_id.id != data['location_id']: + continue + + if not stock_show_initial: + sheet.write(row, 3, stock_total, border_int_right_bold) + sheet.write(row, 4, 'Initial Stock', border_text_center) + stock_show_initial = True + row += 1 + + qty_in = 0 + qty_out = 0 + + if line.location_dest_id.id == data['location_id']: + qty_in = line.qty_done + stock_total += qty_in + + if line.location_id.id == data['location_id']: + qty_out = line.qty_done + stock_total -= qty_out + + sheet.write(row, 0, line.date, border_date_right) + sheet.write(row, 1, qty_in, border_int_right) + sheet.write(row, 2, qty_out, border_int_right) + sheet.write(row, 3, stock_total, border_int_right) + # Distributor + col = 4 + if line.location_dest_id.id == data['location_id']: + if line.picking_id and line.picking_id.origin: + sheet.write(row, col, line.picking_id.partner_id.display_name, border_text_center) + else: + if line.location_id: + if line.location_id.name == 'Inventory adjustment': + sheet.write(row, col, 'Adjust *', border_text_center) + else: + sheet.write(row, col, line.location_id.location_id.name + ' *', border_text_center) + else: + sheet.write(row, col, doc.seller_ids[0].name + ' *' if doc.seller_ids else '', border_text_center) + else: + sheet.write(row, col, '', border_text_center) + # Buyer + col = 5 + if line.location_id.id == data['location_id']: + if line.picking_id and line.picking_id.origin: + sheet.write(row, col, line.picking_id.partner_id.display_name, border_text_center) + else: + if line.location_dest_id: + if line.location_dest_id.name == 'Inventory adjustment': + sheet.write(row, col, 'Adjust *', border_text_center) + else: + sheet.write(row, col, line.location_dest_id.location_id.name + ' *', border_text_center) + else: + sheet.write(row, col, doc.seller_ids[0].name + ' *' if doc.seller_ids else '', border_text_center) + else: + sheet.write(row, col, '', border_text_center) + # Document + col = 6 + if line.picking_id and line.picking_id.origin: + sheet.write(row, col, line.picking_id.name, border_text_center) + else: + sheet.write(row, col, line.reference or '', border_text_center) + # Source Document + col = 7 + if line.picking_id and line.picking_id.origin: + sheet.write(row, col, line.picking_id.origin, border_text_center) + else: + sheet.write(row, col, line.reference or '', border_text_center) + + row += 1 + + row += 3 # jarak antar product + + + + diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 4ab649dc..5b7da705 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -225,7 +225,7 @@ class Keywords(models.Model): tracked_fields = ['keywords', 'category_id', 'product_ids'] neded_sync = any(field in vals for field in tracked_fields) - if neded_sync and self.skip == False: + if neded_sync and self.skip == False and len(self.product_ids) > 0: for record in self: record.solr_flag = 2 diff --git a/indoteknik_custom/views/kartu_stock.xml b/indoteknik_custom/views/kartu_stock.xml new file mode 100644 index 00000000..705d86a2 --- /dev/null +++ b/indoteknik_custom/views/kartu_stock.xml @@ -0,0 +1,17 @@ + + + + + kartu.stok.wizard.form.inherit.single.excel + kartu.stok.wizard + + + + +