From 7d5204a92422848f617af2d0e50d7069bf9f7824 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 30 Dec 2024 14:09:50 +0700 Subject: add form merchant --- indoteknik_custom/models/__init__.py | 2 + indoteknik_custom/models/res_partner.py | 76 +++++++++++++++++- indoteknik_custom/models/user_form_merchant.py | 42 ++++++++++ indoteknik_custom/models/user_merchant_request.py | 96 +++++++++++++++++++++++ 4 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 indoteknik_custom/models/user_form_merchant.py create mode 100644 indoteknik_custom/models/user_merchant_request.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index ad6d75dd..25f56052 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -134,3 +134,5 @@ from . import find_page from . import approval_retur_picking from . import va_multi_approve from . import va_multi_reject +from . import user_form_merchant +from . import user_merchant_request diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index da4a6cb6..79962c2b 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -75,6 +75,30 @@ class ResPartner(models.Model): "Set its value to 0.00 to disable " "this feature", tracking=3) + # MERCHANT + name_merchant = fields.Char(string='Name') + address_merchant = fields.Char(string='Alamat') + state_merchant = fields.Many2one('res.country.state', string='State') + city_merchant = fields.Many2one('vit.kota', string='Kota') + district_merchant = fields.Many2one('vit.kecamatan', string='Kecamatan') + subDistrict_merchant = fields.Many2one('vit.kelurahan', string='Kelurahan') + zip_merchant = fields.Char(string='Kode Pos') + bank_name_merchant = fields.Char(string='Nama Bank') + rekening_name_merchant = fields.Char(string='Nama Rekening') + account_number_merchant = fields.Char(string='Nomor Rekening Bank') + email_company_merchant = fields.Char(string='Email Perusahaan') + email_sales_merchant = fields.Char(string='Email Sales') + email_finance_merchant = fields.Char(string='Email Finance') + phone_merchant = fields.Char(string='No. Telepon Perusahaan') + mobile_merchant = fields.Char(string='No. Handphone') + file_dokumenKtpDirut = fields.Binary(string="KTP Dirut/Direktur", tracking=True, track_visibility="onchange") + file_kartuNama = fields.Binary(string="Kartu Nama", tracking=True, track_visibility="onchange") + file_npwp = fields.Binary(string="NPWP", tracking=True, track_visibility="onchange") + file_sppkp = fields.Binary(string="SPPKP", tracking=True, track_visibility="onchange") + file_suratPernyataan = fields.Binary(string="Surat Pernyataan Nomor Rekening", tracking=True, track_visibility="onchange") + file_fotoKantor = fields.Binary(string="Foto Gudang / Kantor Bagian Depan", tracking=True, track_visibility="onchange") + description = fields.Text(string='Deskripsi') + @api.model def _default_payment_term(self): return self.env.ref('__export__.account_payment_term_26_484409e2').id @@ -136,6 +160,31 @@ class ResPartner(models.Model): vals['company_type_id'] = vals.get('company_type_id', self.company_type_id.id if self.company_type_id else None) + # Merchant + vals['name_merchant'] = vals.get('name_merchant', self.name_merchant) + vals['address_merchant'] = vals.get('address_merchant', self.address_merchant) + vals['state_merchant'] = vals.get('state_merchant', self.state_merchant) + vals['city_merchant'] = vals.get('city_merchant', self.city_merchant) + vals['district_merchant'] = vals.get('district_merchant', self.district_merchant) + vals['subDistrict_merchant'] = vals.get('subDistrict_merchant', self.subDistrict_merchant) + vals['zip_merchant'] = vals.get('zip_merchant', self.zip_merchant) + vals['bank_name_merchant'] = vals.get('bank_name_merchant', self.bank_name_merchant) + vals['rekening_name_merchant'] = vals.get('rekening_name_merchant', self.rekening_name_merchant) + vals['account_number_merchant'] = vals.get('account_number_merchant', self.account_number_merchant) + vals['email_company_merchant'] = vals.get('email_company_merchant', self.email_company_merchant) + vals['email_sales_merchant'] = vals.get('email_sales_merchant', self.email_sales_merchant) + vals['email_finance_merchant'] = vals.get('email_finance_merchant', self.email_finance_merchant) + vals['phone_merchant'] = vals.get('phone_merchant', self.phone_merchant) + vals['mobile_merchant'] = vals.get('mobile_merchant', self.mobile_merchant) + vals['file_dokumenKtpDirut'] = vals.get('file_dokumenKtpDirut', self.file_dokumenKtpDirut) + vals['file_kartuNama'] = vals.get('file_kartuNama', self.file_kartuNama) + vals['file_npwp'] = vals.get('file_npwp', self.file_npwp) + vals['file_sppkp'] = vals.get('file_sppkp', self.file_sppkp) + vals['file_suratPernyataan'] = vals.get('file_suratPernyataan', self.file_suratPernyataan) + vals['file_fotoKantor'] = vals.get('file_fotoKantor', self.file_fotoKantor) + vals['description'] = vals.get('description', self.description) + + # Simpan hanya field yang perlu di-update pada child vals_for_child = { 'customer_type': vals.get('customer_type'), @@ -144,7 +193,32 @@ class ResPartner(models.Model): 'sppkp': vals.get('sppkp'), 'alamat_lengkap_text': vals.get('alamat_lengkap_text'), 'industry_id': vals.get('industry_id'), - 'company_type_id': vals.get('company_type_id') + 'company_type_id': vals.get('company_type_id'), + + # Merchant + 'name_merchant': vals.get('name_merchant'), + 'address_merchant': vals.get('address_merchant'), + 'state_merchant': vals.get('state_merchant'), + 'city_merchant': vals.get('city_merchant'), + 'district_merchant': vals.get('district_merchant'), + 'subDistrict_merchant': vals.get('subDistrict_merchant'), + 'zip_merchant': vals.get('zip_merchant'), + 'bank_name_merchant': vals.get('bank_name_merchant'), + 'rekening_name_merchant': vals.get('rekening_name_merchant'), + 'account_number_merchant': vals.get('account_number_merchant'), + 'email_company_merchant': vals.get('email_company_merchant'), + 'email_sales_merchant': vals.get('email_sales_merchant'), + 'email_finance_merchant': vals.get('email_finance_merchant'), + 'phone_merchant': vals.get('phone_merchant'), + 'mobile_merchant': vals.get('mobile_merchant'), + 'file_dokumenKtpDirut': vals.get('file_dokumenKtpDirut'), + 'file_kartuNama': vals.get('file_kartuNama'), + 'file_npwp': vals.get('file_npwp'), + 'file_sppkp': vals.get('file_sppkp'), + 'file_suratPernyataan': vals.get('file_suratPernyataan'), + 'file_fotoKantor': vals.get('file_fotoKantor'), + 'description': vals.get('description'), + } # Lakukan update pada semua child secara rekursif diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py new file mode 100644 index 00000000..96568f6c --- /dev/null +++ b/indoteknik_custom/models/user_form_merchant.py @@ -0,0 +1,42 @@ +from odoo import models, fields, api +from odoo.exceptions import UserError +from odoo.http import request + + +class UserFormMerchant(models.Model): + _name = 'user.form.merchant' + _inherit = ['mail.thread', 'mail.activity.mixin'] + name = fields.Char(string='Name', required=True) + name_merchant = fields.Char(string='Name') + address = fields.Char(string='Alamat') + state = fields.Many2one('res.country.state', string='State') + city = fields.Many2one('vit.kota', string='Kota') + district = fields.Many2one('vit.kecamatan', string='Kecamatan') + subDistrict = fields.Many2one('vit.kelurahan', string='Kelurahan') + zip = fields.Char(string='Kode Pos') + bank_name = fields.Char(string='Nama Bank') + rekening_name = fields.Char(string='Nama Rekening') + account_number = fields.Char(string='Nomor Rekening Bank') + email_company = fields.Char(string='Email Perusahaan') + email_sales = fields.Char(string='Email Sales') + email_finance = fields.Char(string='Email Finance') + phone = fields.Char(string='No. Telepon Perusahaan') + mobile = fields.Char(string='No. Handphone') + file_dokumenKtpDirut = fields.Binary(string="KTP Dirut/Direktur", tracking=True, track_visibility="onchange") + file_kartuNama = fields.Binary(string="Kartu Nama", tracking=True, track_visibility="onchange") + file_npwp = fields.Binary(string="NPWP", tracking=True, track_visibility="onchange") + file_sppkp = fields.Binary(string="SPPKP", tracking=True, track_visibility="onchange") + file_suratPernyataan = fields.Binary(string="Surat Pernyataan Nomor Rekening", tracking=True, track_visibility="onchange") + file_fotoKantor = fields.Binary(string="Foto Gudang / Kantor Bagian Depan", tracking=True, track_visibility="onchange") + description = fields.Text(string='Deskripsi') + + @api.depends('name', 'name_merchant') + def name_get(self): + result = [] + for record in self: + if record.name_merchant: + display_name = record.name_merchant + else: + display_name = "DETAIL FORM MERCHANT" + result.append((record.id, display_name)) + return result \ No newline at end of file diff --git a/indoteknik_custom/models/user_merchant_request.py b/indoteknik_custom/models/user_merchant_request.py new file mode 100644 index 00000000..b47f6d1d --- /dev/null +++ b/indoteknik_custom/models/user_merchant_request.py @@ -0,0 +1,96 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +from odoo.http import request + + +class RejectReasonWizardMerchant(models.TransientModel): + _name = 'reject.reason.wizard.merchant' + _description = 'Wizard for Reject Reason' + + request_id = fields.Many2one('user.merchant.request', string='Request') + reason_reject = fields.Text(string='Reason for Rejection', required=True) + + def confirm_reject(self): + merchant = self.request_id + if merchant: + merchant.write({'reason_reject': self.reason_reject}) + merchant.state_merchant = 'reject' + return {'type': 'ir.actions.act_window_close'} + + +class ConfirmApprovalWizardMerchant(models.TransientModel): + _name = 'confirm.approval.wizard.merchant' + _description = 'Wizard Konfirmasi Approval' + + merchant_id = fields.Many2one('user.merchant.request', string='Merchant', required=True) + + def confirm_approval(self): + merchant = self.merchant_id + if merchant.state_merchant == 'draft': + merchant.state_merchant = 'approved' + + +class UserMerchantRequest(models.Model): + _name = 'user.merchant.request' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _rec_name = 'user_id' + + user_id = fields.Many2one('res.partner', string='User') + merchant_id = fields.Many2one('user.form.merchant', string='Form Merchant') + user_company_id = fields.Many2one('res.partner', string='Company') + state_merchant = fields.Selection([ + ('draft', 'Pengajuan Merchant'), + ('approved', 'Approved Merchant'), + ('reject', 'Rejected'), + ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft') + reason_reject = fields.Char(string='Reaject Reason') + + def button_approve(self): + for merchant in self: + return { + 'type': 'ir.actions.act_window', + 'name': 'Konfirmasi Approve', + 'res_model': 'confirm.approval.wizard.merchant', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_merchant_id': merchant.id, + }} + + def button_reject(self): + return { + 'type': 'ir.actions.act_window', + 'name': _('Reject Reason'), + 'res_model': 'reject.reason.wizard.merchant', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_request_id': self.id}, + } + + def write(self, vals): + is_approve = True if self.state_merchant == 'approved' or vals.get('state_merchant') == 'approved' else False + if is_approve: + self.user_company_id.name_merchant = self.merchant_id.name_merchant + self.user_company_id.address_merchant = self.merchant_id.address + self.user_company_id.state_merchant = self.merchant_id.state + self.user_company_id.city_merchant = self.merchant_id.city + self.user_company_id.district_merchant = self.merchant_id.district + self.user_company_id.subDistrict_merchant = self.merchant_id.subDistrict + self.user_company_id.zip_merchant = self.merchant_id.zip + self.user_company_id.bank_name_merchant = self.merchant_id.bank_name + self.user_company_id.rekening_name_merchant = self.merchant_id.rekening_name + self.user_company_id.account_number_merchant = self.merchant_id.account_number + self.user_company_id.email_company_merchant = self.merchant_id.email_company + self.user_company_id.email_sales_merchant = self.merchant_id.email_sales + self.user_company_id.email_finance_merchant = self.merchant_id.email_finance + self.user_company_id.phone_merchant = self.merchant_id.phone + self.user_company_id.mobile_merchant = self.merchant_id.mobile + self.user_company_id.file_dokumenKtpDirut = self.merchant_id.file_dokumenKtpDirut + self.user_company_id.file_kartuNama = self.merchant_id.file_kartuNama + self.user_company_id.file_npwp = self.merchant_id.file_npwp + self.user_company_id.file_sppkp = self.merchant_id.file_sppkp + self.user_company_id.file_suratPernyataan = self.merchant_id.file_suratPernyataan + self.user_company_id.file_fotoKantor = self.merchant_id.file_fotoKantor + self.user_company_id.description = self.merchant_id.description + + return super(UserMerchantRequest, self).write(vals) \ No newline at end of file -- cgit v1.2.3 From b2b458d4a642271b98f23403c74eb7aad06cd71d Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 2 Jan 2025 08:52:13 +0700 Subject: update merchant --- indoteknik_custom/models/res_partner.py | 8 ++++++++ indoteknik_custom/models/user_form_merchant.py | 4 ++++ indoteknik_custom/models/user_merchant_request.py | 4 ++++ 3 files changed, 16 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 79962c2b..9e53dae1 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -77,6 +77,7 @@ class ResPartner(models.Model): # MERCHANT name_merchant = fields.Char(string='Name') + pic_merchant = fields.Char(string='PIC Merchant', required=True) address_merchant = fields.Char(string='Alamat') state_merchant = fields.Many2one('res.country.state', string='State') city_merchant = fields.Many2one('vit.kota', string='Kota') @@ -91,12 +92,15 @@ class ResPartner(models.Model): email_finance_merchant = fields.Char(string='Email Finance') phone_merchant = fields.Char(string='No. Telepon Perusahaan') mobile_merchant = fields.Char(string='No. Handphone') + harga_tayang = fields.Char(string='Harga Tayang (HET)') file_dokumenKtpDirut = fields.Binary(string="KTP Dirut/Direktur", tracking=True, track_visibility="onchange") file_kartuNama = fields.Binary(string="Kartu Nama", tracking=True, track_visibility="onchange") file_npwp = fields.Binary(string="NPWP", tracking=True, track_visibility="onchange") file_sppkp = fields.Binary(string="SPPKP", tracking=True, track_visibility="onchange") file_suratPernyataan = fields.Binary(string="Surat Pernyataan Nomor Rekening", tracking=True, track_visibility="onchange") file_fotoKantor = fields.Binary(string="Foto Gudang / Kantor Bagian Depan", tracking=True, track_visibility="onchange") + file_dataProduk = fields.Binary(string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=True,track_visibility="onchange") + file_pricelist = fields.Binary(string="Pricelist", tracking=True, track_visibility="onchange") description = fields.Text(string='Deskripsi') @api.model @@ -162,6 +166,7 @@ class ResPartner(models.Model): # Merchant vals['name_merchant'] = vals.get('name_merchant', self.name_merchant) + vals['pic_merchant'] = vals.get('pic_merchant', self.pic_merchant) vals['address_merchant'] = vals.get('address_merchant', self.address_merchant) vals['state_merchant'] = vals.get('state_merchant', self.state_merchant) vals['city_merchant'] = vals.get('city_merchant', self.city_merchant) @@ -176,12 +181,15 @@ class ResPartner(models.Model): vals['email_finance_merchant'] = vals.get('email_finance_merchant', self.email_finance_merchant) vals['phone_merchant'] = vals.get('phone_merchant', self.phone_merchant) vals['mobile_merchant'] = vals.get('mobile_merchant', self.mobile_merchant) + vals['harga_tayang'] = vals.get('harga_tayang', self.harga_tayang) vals['file_dokumenKtpDirut'] = vals.get('file_dokumenKtpDirut', self.file_dokumenKtpDirut) vals['file_kartuNama'] = vals.get('file_kartuNama', self.file_kartuNama) vals['file_npwp'] = vals.get('file_npwp', self.file_npwp) vals['file_sppkp'] = vals.get('file_sppkp', self.file_sppkp) vals['file_suratPernyataan'] = vals.get('file_suratPernyataan', self.file_suratPernyataan) vals['file_fotoKantor'] = vals.get('file_fotoKantor', self.file_fotoKantor) + vals['file_dataProduk'] = vals.get('file_dataProduk', self.file_dataProduk) + vals['file_pricelist'] = vals.get('file_pricelist', self.file_pricelist) vals['description'] = vals.get('description', self.description) diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py index 96568f6c..31c85481 100644 --- a/indoteknik_custom/models/user_form_merchant.py +++ b/indoteknik_custom/models/user_form_merchant.py @@ -7,6 +7,7 @@ class UserFormMerchant(models.Model): _name = 'user.form.merchant' _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char(string='Name', required=True) + pic_merchant = fields.Char(string='PIC Merchant', required=True) name_merchant = fields.Char(string='Name') address = fields.Char(string='Alamat') state = fields.Many2one('res.country.state', string='State') @@ -22,12 +23,15 @@ class UserFormMerchant(models.Model): email_finance = fields.Char(string='Email Finance') phone = fields.Char(string='No. Telepon Perusahaan') mobile = fields.Char(string='No. Handphone') + harga_tayang = fields.Char(string='Harga Tayang (HET)') file_dokumenKtpDirut = fields.Binary(string="KTP Dirut/Direktur", tracking=True, track_visibility="onchange") file_kartuNama = fields.Binary(string="Kartu Nama", tracking=True, track_visibility="onchange") file_npwp = fields.Binary(string="NPWP", tracking=True, track_visibility="onchange") file_sppkp = fields.Binary(string="SPPKP", tracking=True, track_visibility="onchange") file_suratPernyataan = fields.Binary(string="Surat Pernyataan Nomor Rekening", tracking=True, track_visibility="onchange") file_fotoKantor = fields.Binary(string="Foto Gudang / Kantor Bagian Depan", tracking=True, track_visibility="onchange") + file_dataProduk = fields.Binary(string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=True, track_visibility="onchange") + file_pricelist = fields.Binary(string="Pricelist", tracking=True, track_visibility="onchange") description = fields.Text(string='Deskripsi') @api.depends('name', 'name_merchant') diff --git a/indoteknik_custom/models/user_merchant_request.py b/indoteknik_custom/models/user_merchant_request.py index b47f6d1d..7f6c5f3c 100644 --- a/indoteknik_custom/models/user_merchant_request.py +++ b/indoteknik_custom/models/user_merchant_request.py @@ -71,6 +71,7 @@ class UserMerchantRequest(models.Model): is_approve = True if self.state_merchant == 'approved' or vals.get('state_merchant') == 'approved' else False if is_approve: self.user_company_id.name_merchant = self.merchant_id.name_merchant + self.user_company_id.pic_merchant = self.merchant_id.pic_merchant self.user_company_id.address_merchant = self.merchant_id.address self.user_company_id.state_merchant = self.merchant_id.state self.user_company_id.city_merchant = self.merchant_id.city @@ -85,12 +86,15 @@ class UserMerchantRequest(models.Model): self.user_company_id.email_finance_merchant = self.merchant_id.email_finance self.user_company_id.phone_merchant = self.merchant_id.phone self.user_company_id.mobile_merchant = self.merchant_id.mobile + self.user_company_id.harga_tayang = self.merchant_id.harga_tayang self.user_company_id.file_dokumenKtpDirut = self.merchant_id.file_dokumenKtpDirut self.user_company_id.file_kartuNama = self.merchant_id.file_kartuNama self.user_company_id.file_npwp = self.merchant_id.file_npwp self.user_company_id.file_sppkp = self.merchant_id.file_sppkp self.user_company_id.file_suratPernyataan = self.merchant_id.file_suratPernyataan self.user_company_id.file_fotoKantor = self.merchant_id.file_fotoKantor + self.user_company_id.file_dataProduk = self.merchant_id.file_dataProduk + self.user_company_id.file_pricelist = self.merchant_id.file_pricelist self.user_company_id.description = self.merchant_id.description return super(UserMerchantRequest, self).write(vals) \ No newline at end of file -- cgit v1.2.3 From bd6db8fa22dd602cfaffe0a4b44cc2a794fa1975 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 2 Jan 2025 14:18:46 +0700 Subject: update merchant menu --- indoteknik_custom/models/user_form_merchant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py index 31c85481..18d82e25 100644 --- a/indoteknik_custom/models/user_form_merchant.py +++ b/indoteknik_custom/models/user_form_merchant.py @@ -6,7 +6,7 @@ from odoo.http import request class UserFormMerchant(models.Model): _name = 'user.form.merchant' _inherit = ['mail.thread', 'mail.activity.mixin'] - name = fields.Char(string='Name', required=True) + name = fields.Char(string='Name') pic_merchant = fields.Char(string='PIC Merchant', required=True) name_merchant = fields.Char(string='Name') address = fields.Char(string='Alamat') -- cgit v1.2.3 From 7b3a5a327bcae3b70d2e0bead9921f069c574647 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 13 Jan 2025 15:52:22 +0700 Subject: update code --- indoteknik_custom/models/user_form_merchant.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py index 18d82e25..c8025aa9 100644 --- a/indoteknik_custom/models/user_form_merchant.py +++ b/indoteknik_custom/models/user_form_merchant.py @@ -6,9 +6,14 @@ from odoo.http import request class UserFormMerchant(models.Model): _name = 'user.form.merchant' _inherit = ['mail.thread', 'mail.activity.mixin'] + name = fields.Char(string='Name') - pic_merchant = fields.Char(string='PIC Merchant', required=True) + # informasi peruhaan name_merchant = fields.Char(string='Name') + pejabat_name = fields.Char(string='Pejabat Name') + pic_merchant = fields.Char(string='PIC Merchant') + pic_position = fields.Char(string='Jabatan PIC') + partner_id = fields.Many2one('res.partner', string='Company') address = fields.Char(string='Alamat') state = fields.Many2one('res.country.state', string='State') city = fields.Many2one('vit.kota', string='Kota') @@ -23,6 +28,9 @@ class UserFormMerchant(models.Model): email_finance = fields.Char(string='Email Finance') phone = fields.Char(string='No. Telepon Perusahaan') mobile = fields.Char(string='No. Handphone') + bisnis_type = fields.Char(string='Bisnis Type') + category_perusahaan = fields.Char(string='Kategory Perusahaan') + description = fields.Text(string='Deskripsi') harga_tayang = fields.Char(string='Harga Tayang (HET)') file_dokumenKtpDirut = fields.Binary(string="KTP Dirut/Direktur", tracking=True, track_visibility="onchange") file_kartuNama = fields.Binary(string="Kartu Nama", tracking=True, track_visibility="onchange") @@ -32,7 +40,6 @@ class UserFormMerchant(models.Model): file_fotoKantor = fields.Binary(string="Foto Gudang / Kantor Bagian Depan", tracking=True, track_visibility="onchange") file_dataProduk = fields.Binary(string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=True, track_visibility="onchange") file_pricelist = fields.Binary(string="Pricelist", tracking=True, track_visibility="onchange") - description = fields.Text(string='Deskripsi') @api.depends('name', 'name_merchant') def name_get(self): -- cgit v1.2.3 From ef3ed689e8c8dc1de79e3744055fa12a97aefd35 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 13 Jan 2025 17:03:43 +0700 Subject: update merchant --- indoteknik_custom/models/user_form_merchant.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py index c8025aa9..07990f58 100644 --- a/indoteknik_custom/models/user_form_merchant.py +++ b/indoteknik_custom/models/user_form_merchant.py @@ -29,9 +29,28 @@ class UserFormMerchant(models.Model): phone = fields.Char(string='No. Telepon Perusahaan') mobile = fields.Char(string='No. Handphone') bisnis_type = fields.Char(string='Bisnis Type') - category_perusahaan = fields.Char(string='Kategory Perusahaan') + category_perusahaan = fields.Char(string='Kategori Perusahaan') description = fields.Text(string='Deskripsi') + + # imformasi Vendor harga_tayang = fields.Char(string='Harga Tayang (HET)') + category_produk = fields.Char(string='Kategori Produk') + merk_dagang = fields.Char(string='Merk Dagang') + is_pengajuan_tempo = fields.Boolean(string='Apakah anda memiliki Form Pengajuan Tempo?') + tempo_duration = fields.Many2one('account.payment.term', string='Durasi Tempo') + kredit_limit = fields.Char(string='Kredit Limit') + waktu_pengiriman = fields.Char(string='Waktu Pengiriman') + terhitung_sejak = fields.Char(string='Terhitung Sejak') + + + + # syarat dagang + is_kembali_barang = fields.Char(string='Syarat Pengembalian Barang') # tulis tidak bisa pengembalian + + + + + file_dokumenKtpDirut = fields.Binary(string="KTP Dirut/Direktur", tracking=True, track_visibility="onchange") file_kartuNama = fields.Binary(string="Kartu Nama", tracking=True, track_visibility="onchange") file_npwp = fields.Binary(string="NPWP", tracking=True, track_visibility="onchange") -- cgit v1.2.3 From 932fd84a5eb71d62f2a138f3ddb9ab9c0ccb0385 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Wed, 15 Jan 2025 13:20:21 +0700 Subject: update merchant --- indoteknik_custom/models/user_form_merchant.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py index 07990f58..83ed2eeb 100644 --- a/indoteknik_custom/models/user_form_merchant.py +++ b/indoteknik_custom/models/user_form_merchant.py @@ -29,12 +29,20 @@ class UserFormMerchant(models.Model): phone = fields.Char(string='No. Telepon Perusahaan') mobile = fields.Char(string='No. Handphone') bisnis_type = fields.Char(string='Bisnis Type') + website = fields.Char(string='Website') category_perusahaan = fields.Char(string='Kategori Perusahaan') description = fields.Text(string='Deskripsi') # imformasi Vendor harga_tayang = fields.Char(string='Harga Tayang (HET)') - category_produk = fields.Char(string='Kategori Produk') + category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan', + domain=lambda self: self._get_default_category_domain()) + + @api.model + def _get_default_category_domain(self): + return [('parent_id', '=', False)] + + merk_dagang = fields.Char(string='Merk Dagang') is_pengajuan_tempo = fields.Boolean(string='Apakah anda memiliki Form Pengajuan Tempo?') tempo_duration = fields.Many2one('account.payment.term', string='Durasi Tempo') @@ -45,7 +53,13 @@ class UserFormMerchant(models.Model): # syarat dagang - is_kembali_barang = fields.Char(string='Syarat Pengembalian Barang') # tulis tidak bisa pengembalian + is_kembali_barang = fields.Char(string='Syarat Pengembalian Barang') + tenggat_waktu = fields.Char(string='Tenggat Waktu Perubahan Harga') + sertifikat_produk = fields.Char(string='Dokumen/Sertifikat yang Dimiliki Oleh Brand') + custom_sertifikat_produk = fields.Char(string='Dokumen/Sertifikat Lainnya') + tempo_garansi = fields.Char(string='Garansi') + explain_garansi = fields.Char(string='Garansi Yang Dimaksudkan') + is_order_quantity = fields.Char(string='Apakah Memiliki Minimum Order Quantity (MOQ)') -- cgit v1.2.3 From c7f6d959e0e7a2e8adc45cff515f2b5666f3e732 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 20 Jan 2025 11:56:25 +0700 Subject: update merchant --- indoteknik_custom/models/user_form_merchant.py | 46 +++++++++++++++++--------- 1 file changed, 31 insertions(+), 15 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py index 83ed2eeb..dd143381 100644 --- a/indoteknik_custom/models/user_form_merchant.py +++ b/indoteknik_custom/models/user_form_merchant.py @@ -28,9 +28,19 @@ class UserFormMerchant(models.Model): email_finance = fields.Char(string='Email Finance') phone = fields.Char(string='No. Telepon Perusahaan') mobile = fields.Char(string='No. Handphone') - bisnis_type = fields.Char(string='Bisnis Type') + bisnis_type = fields.Selection([ + ('1', 'PT'), + ('2', 'CV'), + ('3', 'Perorangan'), + ]) website = fields.Char(string='Website') - category_perusahaan = fields.Char(string='Kategori Perusahaan') + category_perusahaan = fields.Selection([ + ('1', 'Principal (Pemegang merk/Produsen)'), + ('2', 'Sole Distributor (Distributor Tunggal)'), + ('3', 'Authorized Distributor (Distributor Resmi)'), + ('4', 'Importer (Pengimpor Barang)'), + ('5', 'Wholesaler (Pedagang Besar)'), + ]) description = fields.Text(string='Deskripsi') # imformasi Vendor @@ -48,7 +58,11 @@ class UserFormMerchant(models.Model): tempo_duration = fields.Many2one('account.payment.term', string='Durasi Tempo') kredit_limit = fields.Char(string='Kredit Limit') waktu_pengiriman = fields.Char(string='Waktu Pengiriman') - terhitung_sejak = fields.Char(string='Terhitung Sejak') + terhitung_sejak = fields.Selection([ + ('1', 'Terima PO'), + ('2', 'Barang Dikirimkan'), + ('3', 'Tukar Faktur'), + ]) @@ -57,22 +71,24 @@ class UserFormMerchant(models.Model): tenggat_waktu = fields.Char(string='Tenggat Waktu Perubahan Harga') sertifikat_produk = fields.Char(string='Dokumen/Sertifikat yang Dimiliki Oleh Brand') custom_sertifikat_produk = fields.Char(string='Dokumen/Sertifikat Lainnya') - tempo_garansi = fields.Char(string='Garansi') + tempo_garansi = fields.Selection([ + ('1', '6 Bulan Garansi'), + ('2', '1 Tahun Garansi'), + ('3', '2 Tahun Garansi'), + ]) explain_garansi = fields.Char(string='Garansi Yang Dimaksudkan') is_order_quantity = fields.Char(string='Apakah Memiliki Minimum Order Quantity (MOQ)') - - - - file_dokumenKtpDirut = fields.Binary(string="KTP Dirut/Direktur", tracking=True, track_visibility="onchange") - file_kartuNama = fields.Binary(string="Kartu Nama", tracking=True, track_visibility="onchange") - file_npwp = fields.Binary(string="NPWP", tracking=True, track_visibility="onchange") - file_sppkp = fields.Binary(string="SPPKP", tracking=True, track_visibility="onchange") - file_suratPernyataan = fields.Binary(string="Surat Pernyataan Nomor Rekening", tracking=True, track_visibility="onchange") - file_fotoKantor = fields.Binary(string="Foto Gudang / Kantor Bagian Depan", tracking=True, track_visibility="onchange") - file_dataProduk = fields.Binary(string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=True, track_visibility="onchange") - file_pricelist = fields.Binary(string="Pricelist", tracking=True, track_visibility="onchange") + # dokumen + file_npwp = fields.Many2one('ir.attachment', string="NPWP Perusahaan", tracking=3) + file_sppkp = fields.Many2one('ir.attachment', string="SPPKP Perusahaan", tracking=3) + file_dokumenKtpDirut = fields.Many2one('ir.attachment', string="KTP Dirut/Direktur", tracking=3) + file_kartuNama = fields.Many2one('ir.attachment', string="Kartu Nama", tracking=3) + file_suratPernyataan = fields.Many2one('ir.attachment', string="Surat Pernyataan Nomor Rekening", tracking=3) + file_fotoKantor = fields.Many2one('ir.attachment', string="Foto Gudang / Kantor Bagian Depan", tracking=3) + file_dataProduk = fields.Many2one('ir.attachment', string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=3) + file_pricelist = fields.Many2one('ir.attachment', string="Pricelist", tracking=3) @api.depends('name', 'name_merchant') def name_get(self): -- cgit v1.2.3 From ff20b62d6932c6be4ffb56f63f3c05be3aa72c06 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 21 Jan 2025 13:43:29 +0700 Subject: update merchant --- indoteknik_custom/models/res_partner.py | 17 +++++++++++++++++ indoteknik_custom/models/user_form_merchant.py | 2 +- indoteknik_custom/models/user_merchant_request.py | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 9e53dae1..d7bd260f 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -76,8 +76,11 @@ class ResPartner(models.Model): "this feature", tracking=3) # MERCHANT + # informasi perusahaan name_merchant = fields.Char(string='Name') + pejabat_name = fields.Char(string='Pejabat Name') pic_merchant = fields.Char(string='PIC Merchant', required=True) + pic_position = fields.Char(string='Jabatan PIC') address_merchant = fields.Char(string='Alamat') state_merchant = fields.Many2one('res.country.state', string='State') city_merchant = fields.Many2one('vit.kota', string='Kota') @@ -92,6 +95,20 @@ class ResPartner(models.Model): email_finance_merchant = fields.Char(string='Email Finance') phone_merchant = fields.Char(string='No. Telepon Perusahaan') mobile_merchant = fields.Char(string='No. Handphone') + bisnis_type = fields.Selection([ + ('1', 'PT'), + ('2', 'CV'), + ('3', 'Perorangan'), + ]) + website_merchant = fields.Char(string='Website') + category_perusahaan = fields.Selection([ + ('1', 'Principal (Pemegang merk/Produsen)'), + ('2', 'Sole Distributor (Distributor Tunggal)'), + ('3', 'Authorized Distributor (Distributor Resmi)'), + ('4', 'Importer (Pengimpor Barang)'), + ('5', 'Wholesaler (Pedagang Besar)'), + ]) + harga_tayang = fields.Char(string='Harga Tayang (HET)') file_dokumenKtpDirut = fields.Binary(string="KTP Dirut/Direktur", tracking=True, track_visibility="onchange") file_kartuNama = fields.Binary(string="Kartu Nama", tracking=True, track_visibility="onchange") diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py index dd143381..6d881621 100644 --- a/indoteknik_custom/models/user_form_merchant.py +++ b/indoteknik_custom/models/user_form_merchant.py @@ -8,7 +8,7 @@ class UserFormMerchant(models.Model): _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char(string='Name') - # informasi peruhaan + # informasi peruhsaan name_merchant = fields.Char(string='Name') pejabat_name = fields.Char(string='Pejabat Name') pic_merchant = fields.Char(string='PIC Merchant') diff --git a/indoteknik_custom/models/user_merchant_request.py b/indoteknik_custom/models/user_merchant_request.py index 7f6c5f3c..a373a771 100644 --- a/indoteknik_custom/models/user_merchant_request.py +++ b/indoteknik_custom/models/user_merchant_request.py @@ -70,6 +70,7 @@ class UserMerchantRequest(models.Model): def write(self, vals): is_approve = True if self.state_merchant == 'approved' or vals.get('state_merchant') == 'approved' else False if is_approve: + # Informasi Perusahaan self.user_company_id.name_merchant = self.merchant_id.name_merchant self.user_company_id.pic_merchant = self.merchant_id.pic_merchant self.user_company_id.address_merchant = self.merchant_id.address -- cgit v1.2.3 From 6809954d3029c98766d323eda40e6bd1fda8be0a Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 21 Jan 2025 16:34:18 +0700 Subject: update merchant contact --- indoteknik_custom/models/res_partner.py | 110 +++++++++++++++++++--- indoteknik_custom/models/user_form_merchant.py | 4 - indoteknik_custom/models/user_merchant_request.py | 28 +++++- 3 files changed, 122 insertions(+), 20 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 8a420048..7852682f 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -182,20 +182,52 @@ class ResPartner(models.Model): ('4', 'Importer (Pengimpor Barang)'), ('5', 'Wholesaler (Pedagang Besar)'), ]) - + # Informasi Vendor harga_tayang = fields.Char(string='Harga Tayang (HET)') - file_dokumenKtpDirut = fields.Binary(string="KTP Dirut/Direktur", tracking=True, track_visibility="onchange") - file_kartuNama = fields.Binary(string="Kartu Nama", tracking=True, track_visibility="onchange") - file_npwp = fields.Binary(string="NPWP", tracking=True, track_visibility="onchange") - file_sppkp = fields.Binary(string="SPPKP", tracking=True, track_visibility="onchange") - file_suratPernyataan = fields.Binary(string="Surat Pernyataan Nomor Rekening", tracking=True, - track_visibility="onchange") - file_fotoKantor = fields.Binary(string="Foto Gudang / Kantor Bagian Depan", tracking=True, - track_visibility="onchange") - file_dataProduk = fields.Binary(string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=True, - track_visibility="onchange") - file_pricelist = fields.Binary(string="Pricelist", tracking=True, track_visibility="onchange") - description = fields.Text(string='Deskripsi') + category_produk_ids_merchant = fields.Many2many( + 'product.public.category', + string='Kategori Produk Merchant', + domain=lambda self: self._get_default_category_domain(), + relation='res_partner_category_produk_ids_merchant_rel' # Nama tabel relasi berbeda + ) + + @api.model + def _get_default_category_domain(self): + return [('parent_id', '=', False)] + + merk_dagang = fields.Char(string='Merk Dagang') + is_pengajuan_tempo = fields.Boolean(string='Apakah anda memiliki Form Pengajuan Tempo?') + tempo_duration_merchant = fields.Many2one('account.payment.term', string='Durasi Tempo') + kredit_limit = fields.Char(string='Kredit Limit') + waktu_pengiriman = fields.Char(string='Waktu Pengiriman') + terhitung_sejak = fields.Selection([ + ('1', 'Terima PO'), + ('2', 'Barang Dikirimkan'), + ('3', 'Tukar Faktur'), + ]) + + # syarat dagang + is_kembali_barang = fields.Char(string='Syarat Pengembalian Barang') + tenggat_waktu = fields.Char(string='Tenggat Waktu Perubahan Harga') + sertifikat_produk = fields.Char(string='Dokumen/Sertifikat yang Dimiliki Oleh Brand') + custom_sertifikat_produk = fields.Char(string='Dokumen/Sertifikat Lainnya') + tempo_garansi = fields.Selection([ + ('1', '6 Bulan Garansi'), + ('2', '1 Tahun Garansi'), + ('3', '2 Tahun Garansi'), + ]) + explain_garansi = fields.Char(string='Garansi Yang Dimaksudkan') + is_order_quantity = fields.Char(string='Apakah Memiliki Minimum Order Quantity (MOQ)') + + # dokumen + file_npwp = fields.Many2one('ir.attachment', string="NPWP Perusahaan", tracking=3) + file_sppkp = fields.Many2one('ir.attachment', string="SPPKP Perusahaan", tracking=3) + file_dokumenKtpDirut = fields.Many2one('ir.attachment', string="KTP Dirut/Direktur", tracking=3) + file_kartuNama = fields.Many2one('ir.attachment', string="Kartu Nama", tracking=3) + file_suratPernyataan = fields.Many2one('ir.attachment', string="Surat Pernyataan Nomor Rekening", tracking=3) + file_fotoKantor = fields.Many2one('ir.attachment', string="Foto Gudang / Kantor Bagian Depan", tracking=3) + file_dataProduk = fields.Many2one('ir.attachment', string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=3) + file_pricelist = fields.Many2one('ir.attachment', string="Pricelist", tracking=3) @api.model def _default_payment_term(self): @@ -323,9 +355,12 @@ class ResPartner(models.Model): vals['dokumen_foto_kantor'] = vals.get('dokumen_foto_kantor', self.dokumen_foto_kantor) vals['dokumen_tempat_bekerja'] = vals.get('dokumen_tempat_bekerja', self.dokumen_tempat_bekerja) - # Merchant + # MERCHANT + # Informasi Perusahaan vals['name_merchant'] = vals.get('name_merchant', self.name_merchant) + vals['pejabat_name'] = vals.get('pejabat_name', self.pejabat_name) vals['pic_merchant'] = vals.get('pic_merchant', self.pic_merchant) + vals['pic_position'] = vals.get('pic_position', self.pic_position) vals['address_merchant'] = vals.get('address_merchant', self.address_merchant) vals['state_merchant'] = vals.get('state_merchant', self.state_merchant) vals['city_merchant'] = vals.get('city_merchant', self.city_merchant) @@ -340,7 +375,30 @@ class ResPartner(models.Model): vals['email_finance_merchant'] = vals.get('email_finance_merchant', self.email_finance_merchant) vals['phone_merchant'] = vals.get('phone_merchant', self.phone_merchant) vals['mobile_merchant'] = vals.get('mobile_merchant', self.mobile_merchant) + vals['bisnis_type'] = vals.get('bisnis_type', self.bisnis_type) + vals['website_merchant'] = vals.get('website_merchant', self.website_merchant) + vals['category_perusahaan'] = vals.get('category_perusahaan', self.category_perusahaan) + + # Informasi Vendor vals['harga_tayang'] = vals.get('harga_tayang', self.harga_tayang) + vals['category_produk_ids_merchant'] = vals.get('category_produk_ids_merchant', self.category_produk_ids_merchant) + vals['merk_dagang'] = vals.get('merk_dagang', self.merk_dagang) + vals['is_pengajuan_tempo'] = vals.get('is_pengajuan_tempo', self.is_pengajuan_tempo) + vals['tempo_duration_merchant'] = vals.get('tempo_duration_merchant', self.tempo_duration_merchant) + vals['kredit_limit'] = vals.get('kredit_limit', self.kredit_limit) + vals['waktu_pengiriman'] = vals.get('waktu_pengiriman', self.waktu_pengiriman) + vals['terhitung_sejak'] = vals.get('terhitung_sejak', self.terhitung_sejak) + + # Syarat Dagang + vals['is_kembali_barang'] = vals.get('is_kembali_barang', self.is_kembali_barang) + vals['tenggat_waktu'] = vals.get('tenggat_waktu', self.tenggat_waktu) + vals['sertifikat_produk'] = vals.get('sertifikat_produk', self.sertifikat_produk) + vals['custom_sertifikat_produk'] = vals.get('custom_sertifikat_produk', self.custom_sertifikat_produk) + vals['tempo_garansi'] = vals.get('tempo_garansi', self.tempo_garansi) + vals['explain_garansi'] = vals.get('explain_garansi', self.explain_garansi) + vals['is_order_quantity'] = vals.get('is_order_quantity', self.is_order_quantity) + + # Dokumen vals['file_dokumenKtpDirut'] = vals.get('file_dokumenKtpDirut', self.file_dokumenKtpDirut) vals['file_kartuNama'] = vals.get('file_kartuNama', self.file_kartuNama) vals['file_npwp'] = vals.get('file_npwp', self.file_npwp) @@ -423,6 +481,9 @@ class ResPartner(models.Model): # Merchant 'name_merchant': vals.get('name_merchant'), + 'pejabat_name': vals.get('pejabat_name'), + 'pic_merchant': vals.get('pic_merchant'), + 'pic_position': vals.get('pic_position'), 'address_merchant': vals.get('address_merchant'), 'state_merchant': vals.get('state_merchant'), 'city_merchant': vals.get('city_merchant'), @@ -437,12 +498,33 @@ class ResPartner(models.Model): 'email_finance_merchant': vals.get('email_finance_merchant'), 'phone_merchant': vals.get('phone_merchant'), 'mobile_merchant': vals.get('mobile_merchant'), + 'bisnis_type': vals.get('bisnis_type'), + 'website_merchant': vals.get('website_merchant'), + 'category_perusahaan': vals.get('category_perusahaan'), + 'harga_tayang': vals.get('harga_tayang'), + 'category_produk_ids_merchant': vals.get('category_produk_ids_merchant'), + 'merk_dagang': vals.get('merk_dagang'), + 'is_pengajuan_tempo': vals.get('is_pengajuan_tempo'), + 'tempo_duration_merchant': vals.get('tempo_duration_merchant'), + 'kredit_limit': vals.get('kredit_limit'), + 'waktu_pengiriman': vals.get('waktu_pengiriman'), + 'terhitung_sejak': vals.get('terhitung_sejak'), + 'is_kembali_barang': vals.get('is_kembali_barang'), + 'tenggat_waktu': vals.get('tenggat_waktu'), + 'sertifikat_produk': vals.get('sertifikat_produk'), + 'custom_sertifikat_produk': vals.get('custom_sertifikat_produk'), + 'tempo_garansi': vals.get('tempo_garansi'), + 'explain_garansi': vals.get('explain_garansi'), + 'is_order_quantity': vals.get('is_order_quantity'), + 'file_dokumenKtpDirut': vals.get('file_dokumenKtpDirut'), 'file_kartuNama': vals.get('file_kartuNama'), 'file_npwp': vals.get('file_npwp'), 'file_sppkp': vals.get('file_sppkp'), 'file_suratPernyataan': vals.get('file_suratPernyataan'), 'file_fotoKantor': vals.get('file_fotoKantor'), + 'file_dataProduk': vals.get('file_dataProduk'), + 'file_pricelist': vals.get('file_pricelist'), 'description': vals.get('description'), } diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py index 6d881621..a804e93f 100644 --- a/indoteknik_custom/models/user_form_merchant.py +++ b/indoteknik_custom/models/user_form_merchant.py @@ -52,7 +52,6 @@ class UserFormMerchant(models.Model): def _get_default_category_domain(self): return [('parent_id', '=', False)] - merk_dagang = fields.Char(string='Merk Dagang') is_pengajuan_tempo = fields.Boolean(string='Apakah anda memiliki Form Pengajuan Tempo?') tempo_duration = fields.Many2one('account.payment.term', string='Durasi Tempo') @@ -64,8 +63,6 @@ class UserFormMerchant(models.Model): ('3', 'Tukar Faktur'), ]) - - # syarat dagang is_kembali_barang = fields.Char(string='Syarat Pengembalian Barang') tenggat_waktu = fields.Char(string='Tenggat Waktu Perubahan Harga') @@ -79,7 +76,6 @@ class UserFormMerchant(models.Model): explain_garansi = fields.Char(string='Garansi Yang Dimaksudkan') is_order_quantity = fields.Char(string='Apakah Memiliki Minimum Order Quantity (MOQ)') - # dokumen file_npwp = fields.Many2one('ir.attachment', string="NPWP Perusahaan", tracking=3) file_sppkp = fields.Many2one('ir.attachment', string="SPPKP Perusahaan", tracking=3) diff --git a/indoteknik_custom/models/user_merchant_request.py b/indoteknik_custom/models/user_merchant_request.py index a373a771..dd571cdc 100644 --- a/indoteknik_custom/models/user_merchant_request.py +++ b/indoteknik_custom/models/user_merchant_request.py @@ -72,7 +72,9 @@ class UserMerchantRequest(models.Model): if is_approve: # Informasi Perusahaan self.user_company_id.name_merchant = self.merchant_id.name_merchant + self.user_company_id.pejabat_name = self.merchant_id.pejabat_name self.user_company_id.pic_merchant = self.merchant_id.pic_merchant + self.user_company_id.pic_position = self.merchant_id.pic_position self.user_company_id.address_merchant = self.merchant_id.address self.user_company_id.state_merchant = self.merchant_id.state self.user_company_id.city_merchant = self.merchant_id.city @@ -87,11 +89,33 @@ class UserMerchantRequest(models.Model): self.user_company_id.email_finance_merchant = self.merchant_id.email_finance self.user_company_id.phone_merchant = self.merchant_id.phone self.user_company_id.mobile_merchant = self.merchant_id.mobile + self.user_company_id.bisnis_type = self.merchant_id.bisnis_type + self.user_company_id.website_merchant = self.merchant_id.website + self.user_company_id.category_perusahaan = self.merchant_id.category_perusahaan + + # Informasi Vendor self.user_company_id.harga_tayang = self.merchant_id.harga_tayang - self.user_company_id.file_dokumenKtpDirut = self.merchant_id.file_dokumenKtpDirut - self.user_company_id.file_kartuNama = self.merchant_id.file_kartuNama + self.user_company_id.category_produk_ids_merchant = self.merchant_id.category_produk_ids + self.user_company_id.merk_dagang = self.merchant_id.merk_dagang + self.user_company_id.is_pengajuan_tempo = self.merchant_id.is_pengajuan_tempo + self.user_company_id.tempo_duration_merchant = self.merchant_id.tempo_duration + self.user_company_id.kredit_limit = self.merchant_id.kredit_limit + self.user_company_id.waktu_pengiriman = self.merchant_id.waktu_pengiriman + self.user_company_id.terhitung_sejak = self.merchant_id.terhitung_sejak + + # Syarat Perdagangan + self.user_company_id.is_kembali_barang = self.merchant_id.is_kembali_barang + self.user_company_id.tenggat_waktu = self.merchant_id.tenggat_waktu + self.user_company_id.sertifikat_produk = self.merchant_id.sertifikat_produk + self.user_company_id.tempo_garansi = self.merchant_id.tempo_garansi + self.user_company_id.explain_garansi = self.merchant_id.explain_garansi + self.user_company_id.is_order_quantity = self.merchant_id.is_order_quantity + + # Dokumen self.user_company_id.file_npwp = self.merchant_id.file_npwp self.user_company_id.file_sppkp = self.merchant_id.file_sppkp + self.user_company_id.file_dokumenKtpDirut = self.merchant_id.file_dokumenKtpDirut + self.user_company_id.file_kartuNama = self.merchant_id.file_kartuNama self.user_company_id.file_suratPernyataan = self.merchant_id.file_suratPernyataan self.user_company_id.file_fotoKantor = self.merchant_id.file_fotoKantor self.user_company_id.file_dataProduk = self.merchant_id.file_dataProduk -- cgit v1.2.3 From 8c87a6c35b2242ee1804e1955bbb216c8c86de4d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 10 Feb 2025 09:44:05 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cc86c451..ec761900 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1217,4 +1217,19 @@ class BarcodeProduct(models.Model): if record.barcode and not record.product_id.barcode: record.product_id.barcode = record.barcode else: - raise UserError('Barcode sudah terisi') \ No newline at end of file + raise UserError('Barcode sudah terisi') + +class CheckKoli(models.Model): + _name = 'check.koli' + _description = 'Check Koli' + _order = 'picking_id, id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + product_id = fields.(string='Koli') \ No newline at end of file -- cgit v1.2.3 From c99bf4c49859450ce4cb081c920edda2077b3b1a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 11 Feb 2025 09:48:55 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 34 ++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ec761900..3f888b02 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -16,6 +16,8 @@ _logger = logging.getLogger(__name__) class StockPicking(models.Model): _inherit = 'stock.picking' + scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True) + check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True) check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') @@ -122,6 +124,8 @@ class StockPicking(models.Model): ('cancel', 'Cancelled'), ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") notee = fields.Text(string="Note") + quantity_koli = fields.Float(string="Quantity Koli") + source_koli_id = fields.Many2one('stock.picking', string="Source Koli") @api.model def _compute_dokumen_tanda_terima(self): @@ -166,6 +170,14 @@ class StockPicking(models.Model): lalamove_image_url = fields.Char(string="Lalamove Image URL") lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html") + @api.onchange('quantity_koli') + def _onchange_quantity_koli(self): + self.check_koli_lines = [(5, 0, 0)] + self.check_koli_lines = [(0, 0, { + 'koli': f"{self.name}/{str(i+1).zfill(3)}", + 'picking_id': self.id, + }) for i in range(int(self.quantity_koli))] + def _compute_lalamove_image_html(self): for record in self: if record.lalamove_image_url: @@ -1223,6 +1235,7 @@ class CheckKoli(models.Model): _name = 'check.koli' _description = 'Check Koli' _order = 'picking_id, id' + _rec_name = 'koli' picking_id = fields.Many2one( 'stock.picking', @@ -1232,4 +1245,23 @@ class CheckKoli(models.Model): index=True, copy=False, ) - product_id = fields.(string='Koli') \ No newline at end of file + koli = fields.Char(string='Koli') + +class ScanKoli(models.Model): + _name = 'scan.koli' + _description = 'Scan Koli' + _order = 'picking_id, id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + koli_id = fields.Many2one('check.koli', string='Koli') + + @api.constrains('koli_id') + def _constrains_koli_id(self): + self.picking_id.source_koli_id = self.koli_id.picking_id.id \ No newline at end of file -- cgit v1.2.3 From 99b252edaefc372fcd4ef59e065284d8feeb669c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Feb 2025 09:38:46 +0700 Subject: push --- indoteknik_custom/models/__init__.py | 2 + indoteknik_custom/models/sale_order.py | 1 + indoteknik_custom/models/sales_order_koli.py | 25 +++ .../models/stock_backorder_confirmation.py | 50 ++++++ indoteknik_custom/models/stock_picking.py | 171 ++++++++++++++++++++- 5 files changed, 243 insertions(+), 6 deletions(-) create mode 100644 indoteknik_custom/models/sales_order_koli.py create mode 100644 indoteknik_custom/models/stock_backorder_confirmation.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index ed9e91da..a5297806 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -140,3 +140,5 @@ from . import va_multi_reject from . import stock_immediate_transfer from . import coretax_fatur from . import barcoding_product +from . import sales_order_koli +from . import stock_backorder_confirmation diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 7b2d9bf8..88c32fb6 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -11,6 +11,7 @@ _logger = logging.getLogger(__name__) class SaleOrder(models.Model): _inherit = "sale.order" + koli_lines = fields.One2many('sales.order.koli', 'sale_order_id', string='Sales Order Koli', auto_join=True) fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2') fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment') reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines') diff --git a/indoteknik_custom/models/sales_order_koli.py b/indoteknik_custom/models/sales_order_koli.py new file mode 100644 index 00000000..02e85256 --- /dev/null +++ b/indoteknik_custom/models/sales_order_koli.py @@ -0,0 +1,25 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +import logging + +_logger = logging.getLogger(__name__) + + +class SalesOrderKoli(models.Model): + _name = 'sales.order.koli' + _description = 'Sales Order Koli' + _order = 'sale_order_id, id' + _rec_name = 'koli_id' + + sale_order_id = fields.Many2one( + 'sale.order', + string='Sale Order Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + koli_id = fields.Many2one('check.koli', string='Koli') + picking_id = fields.Many2one('stock.picking', string='Picking') + diff --git a/indoteknik_custom/models/stock_backorder_confirmation.py b/indoteknik_custom/models/stock_backorder_confirmation.py new file mode 100644 index 00000000..0fd7c34e --- /dev/null +++ b/indoteknik_custom/models/stock_backorder_confirmation.py @@ -0,0 +1,50 @@ +from odoo import models, fields, api +from odoo.tools.float_utils import float_compare + +class StockBackorderConfirmation(models.TransientModel): + _inherit = 'stock.backorder.confirmation' + + def process(self): + res = super(StockBackorderConfirmation, self).process() + + pickings_to_do = self.env['stock.picking'] + for line in self.backorder_confirmation_line_ids: + if line.to_backorder: + pickings_to_do |= line.picking_id + + for pick in pickings_to_do: + # Mencari backorder yang baru terbentuk + backorder = self.env['stock.picking'].search([('backorder_id', '=', pick.id)], limit=1) + + if backorder: + # Cari BU/OUT terbaru berdasarkan sale_id + latest_out_picking = self.env['stock.picking'].search([ + ('sale_id', '=', pick.sale_id.id), + ('picking_type_id.code', '=', 'outgoing') + ], order='id desc', limit=1) + + # Update linked_out_picking_id pada backorder BU/PICK + if latest_out_picking: + backorder.linked_out_picking_id = latest_out_picking.id + else: + backorder.linked_out_picking_id = pick.linked_out_picking_id + + # 🚀 Cek apakah ada backorder baru dari BU/OUT + for pick in self.env['stock.picking'].search([ + ('picking_type_id.code', '=', 'outgoing'), + ('backorder_id', '!=', False) + ]): + # Backorder BU/OUT terbaru + latest_out_backorder = self.env['stock.picking'].search([ + ('backorder_id', '=', pick.id) + ], order='id desc', limit=1) + + if latest_out_backorder: + # 🚀 Update semua BU/PICK yang belum `done` atau `cancel` + self.env['stock.picking'].search([ + ('sale_id', '=', pick.sale_id.id), + ('picking_type_id.code', '=', 'incoming'), + ('state', 'not in', ['done', 'cancel']) + ]).write({'linked_out_picking_id': latest_out_backorder.id}) + + return res diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3f888b02..02ce819f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -124,7 +124,7 @@ class StockPicking(models.Model): ('cancel', 'Cancelled'), ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") notee = fields.Text(string="Note") - quantity_koli = fields.Float(string="Quantity Koli") + quantity_koli = fields.Float(string="Quantity Koli", copy=False) source_koli_id = fields.Many2one('stock.picking', string="Source Koli") @api.model @@ -170,6 +170,54 @@ class StockPicking(models.Model): lalamove_image_url = fields.Char(string="Lalamove Image URL") lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html") + total_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli") + total_koli = fields.Integer(compute='_compute_total_koli', string="Total Koli") + total_koli_display = fields.Char(compute='_compute_total_koli_display', string="Total Koli Display") + linked_out_picking_id = fields.Many2one('stock.picking', string="Linked BU/OUT", copy=False) + backorder_picking_id = fields.Many2one('stock.picking', string="Backorder Picking", copy=False) + + def action_create_backorder(self): + """ Override method to handle backorder logic automatically """ + backorder = super(StockPicking, self).action_create_backorder() + + for picking in self: + if 'BU/PICK/' in picking.name: + # Jika BU/PICK memiliki BU/OUT yang terhubung + if picking.linked_out_picking_id: + out_picking = picking.linked_out_picking_id + out_backorder = out_picking.backorder_picking_id + + # Jika BU/OUT belum punya backorder, hubungkan BU/PICK backorder ke BU/OUT lama + if not out_backorder: + backorder.linked_out_picking_id = out_picking + else: + # Jika BU/OUT sudah punya backorder, hubungkan ke backorder BU/OUT + backorder.linked_out_picking_id = out_backorder + + elif 'BU/OUT/' in picking.name: + # Jika BU/OUT membuat backorder, update semua BU/PICK yang terhubung ke BU/OUT lama + pickings_to_update = self.env['stock.picking'].search([('linked_out_picking_id', '=', picking.id)]) + for pick in pickings_to_update: + pick.linked_out_picking_id = backorder + + return backorder + + + @api.depends('total_so_koli') # Sesuaikan dengan field yang relevan + def _compute_total_so_koli(self): + for picking in self: + picking.total_so_koli = self.env['check.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id)]) + + @api.depends('total_koli') # Sesuaikan dengan field yang relevan + def _compute_total_koli(self): + for picking in self: + picking.total_koli = self.env['scan.koli'].search_count([('picking_id', '=', picking.id)]) + + @api.depends('total_koli', 'total_so_koli') + def _compute_total_koli_display(self): + for picking in self: + picking.total_koli_display = f"{picking.total_koli} / {picking.total_so_koli}" + @api.onchange('quantity_koli') def _onchange_quantity_koli(self): self.check_koli_lines = [(5, 0, 0)] @@ -772,6 +820,9 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): + if self.total_koli != self.total_so_koli: + raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") + % (self.total_koli, self.total_so_koli)) if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") @@ -819,11 +870,40 @@ class StockPicking(models.Model): self.validation_minus_onhand_quantity() self.responsible = self.env.user.id + if self.picking_type_code == 'internal' and 'BU/PICK/' in self.name: + self.send_koli_to_so() + if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name: + self.check_koli() res = super(StockPicking, self).button_validate() self.calculate_line_no() self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' return res + + + def check_koli(self): + for picking in self: + sale_id = picking.sale_id + for koli_lines in picking.scan_koli_lines: + if koli_lines.koli_id.sale_order_id != sale_id: + raise UserError('Koli tidak sesuai') + + def send_koli_to_so(self): + for picking in self: + for koli_line in picking.check_koli_lines: + existing_koli = self.env['sales.order.koli'].search([ + ('sale_order_id', '=', picking.sale_id.id), + ('picking_id', '=', picking.id), + ('koli_id', '=', koli_line.id) + ], limit=1) + + if not existing_koli: # Hindari duplikasi + self.env['sales.order.koli'].create({ + 'sale_order_id': picking.sale_id.id, + 'picking_id': picking.id, + 'koli_id': koli_line.id + }) + def check_qty_done_stock(self): @@ -901,6 +981,34 @@ class StockPicking(models.Model): res = super(StockPicking, self).action_cancel() return res + + def write(self, vals_list): + """ Override write method to auto-link BU/PICK to BU/OUT when necessary """ + records = super(StockPicking, self).write(vals_list) + for picking in records: + if 'BU/OUT/' in picking.name: + # Cari BU/PICK yang berhubungan berdasarkan logika tertentu + pick_picking = self.env['stock.picking'].search([ + ('name', 'like', 'BU/PICK/%'), + ('linked_out_picking_id', '=', False), + ('sale_id', '=', picking.sale_id.id) + ], limit=1) + + if pick_picking: + pick_picking.linked_out_picking_id = picking + + if 'BU/PICK/' in picking.name: + # Cari BU/PICK yang berhubungan berdasarkan logika tertentu + pick_picking = self.env['stock.picking'].search([ + ('name', 'like', 'BU/OUT/%'), + ('state', 'not in', ['cancel', 'done']), + ('sale_id', '=', picking.sale_id.id) + ], limit=1) + + if pick_picking: + pick_picking.linked_out_picking_id = picking + + return records @api.model @@ -917,7 +1025,20 @@ class StockPicking(models.Model): if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', vals.get('company_id'))]) > 0: new_name = f"{new_name}-DUP" vals['name'] = new_name - return super(StockPicking, self).create(vals) + records = super(StockPicking, self).create(vals) + for picking in records: + if 'BU/OUT/' in picking.name: + # Cari BU/PICK yang berhubungan berdasarkan logika tertentu + pick_picking = self.env['stock.picking'].search([ + ('name', 'like', 'BU/PICK'), + ('linked_out_picking_id', '=', False), + ('origin', '=', picking.origin) + ], limit=1) + + if pick_picking: + pick_picking.linked_out_picking_id = picking + + return records def write(self, vals): self._use_faktur(vals) @@ -1251,6 +1372,7 @@ class ScanKoli(models.Model): _name = 'scan.koli' _description = 'Scan Koli' _order = 'picking_id, id' + _rec_name = 'koli_id' picking_id = fields.Many2one( 'stock.picking', @@ -1260,8 +1382,45 @@ class ScanKoli(models.Model): index=True, copy=False, ) - koli_id = fields.Many2one('check.koli', string='Koli') + koli_id = fields.Many2one('sales.order.koli', string='Koli') + scan_koli_progress = fields.Char( + string="Progress Scan Koli", + compute="_compute_scan_koli_progress" + ) + + def _compute_scan_koli_progress(self): + """ Menghitung progres scan koli dalam format 'X/Y' """ + for scan in self: + if scan.picking_id: + all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') + scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan + total_so_koli = scan.picking_id.total_so_koli + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" + + @api.model_create_multi + def create(self, vals_list): + """ Override create untuk update progress scan setelah scan koli ditambahkan """ + records = super(ScanKoli, self).create(vals_list) + for record in records: + record._compute_scan_koli_progress() + return records + + @api.constrains('picking_id', 'picking_id.total_so_koli') + def _check_koli_validation(self): + """ Validasi jika jumlah scan koli melebihi total SO koli """ + for scan in self: + total_scans = len(scan.picking_id.scan_koli_lines) + if total_scans > scan.picking_id.total_so_koli: + raise UserError(_("Jumlah scan koli melebihi total SO koli!")) + + @api.onchange('koli_id') + def _onchange_koli_id(self): + if not self.koli_id: + return + + source_koli_so = self.picking_id.ids # Picking asal dari Koli yang dipilih + source_koli = self.koli_id.picking_id.linked_out_picking_id.ids - @api.constrains('koli_id') - def _constrains_koli_id(self): - self.picking_id.source_koli_id = self.koli_id.picking_id.id \ No newline at end of file + # Cek apakah source_koli ditemukan + if source_koli_so != source_koli: + raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) -- cgit v1.2.3 From c1810b315d820a184db47d551b39700ce00d1440 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Feb 2025 09:57:56 +0700 Subject: push wms --- indoteknik_custom/models/sales_order_koli.py | 1 + .../models/stock_backorder_confirmation.py | 63 +++---- .../models/stock_immediate_transfer.py | 8 +- indoteknik_custom/models/stock_picking.py | 192 +++++++++------------ 4 files changed, 106 insertions(+), 158 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sales_order_koli.py b/indoteknik_custom/models/sales_order_koli.py index 02e85256..c782a40e 100644 --- a/indoteknik_custom/models/sales_order_koli.py +++ b/indoteknik_custom/models/sales_order_koli.py @@ -22,4 +22,5 @@ class SalesOrderKoli(models.Model): ) koli_id = fields.Many2one('check.koli', string='Koli') picking_id = fields.Many2one('stock.picking', string='Picking') + state = fields.Selection([('not_delivered', 'Not Delivered'), ('delivered', 'Delivered')], string='Status', default='not_delivered') diff --git a/indoteknik_custom/models/stock_backorder_confirmation.py b/indoteknik_custom/models/stock_backorder_confirmation.py index 0fd7c34e..f4da4cb5 100644 --- a/indoteknik_custom/models/stock_backorder_confirmation.py +++ b/indoteknik_custom/models/stock_backorder_confirmation.py @@ -5,46 +5,29 @@ class StockBackorderConfirmation(models.TransientModel): _inherit = 'stock.backorder.confirmation' def process(self): - res = super(StockBackorderConfirmation, self).process() - pickings_to_do = self.env['stock.picking'] + pickings_not_to_do = self.env['stock.picking'] for line in self.backorder_confirmation_line_ids: - if line.to_backorder: + line.picking_id.send_mail_bills() + line.picking_id.send_koli_to_so() + if line.to_backorder is True: pickings_to_do |= line.picking_id - - for pick in pickings_to_do: - # Mencari backorder yang baru terbentuk - backorder = self.env['stock.picking'].search([('backorder_id', '=', pick.id)], limit=1) - - if backorder: - # Cari BU/OUT terbaru berdasarkan sale_id - latest_out_picking = self.env['stock.picking'].search([ - ('sale_id', '=', pick.sale_id.id), - ('picking_type_id.code', '=', 'outgoing') - ], order='id desc', limit=1) - - # Update linked_out_picking_id pada backorder BU/PICK - if latest_out_picking: - backorder.linked_out_picking_id = latest_out_picking.id - else: - backorder.linked_out_picking_id = pick.linked_out_picking_id - - # 🚀 Cek apakah ada backorder baru dari BU/OUT - for pick in self.env['stock.picking'].search([ - ('picking_type_id.code', '=', 'outgoing'), - ('backorder_id', '!=', False) - ]): - # Backorder BU/OUT terbaru - latest_out_backorder = self.env['stock.picking'].search([ - ('backorder_id', '=', pick.id) - ], order='id desc', limit=1) - - if latest_out_backorder: - # 🚀 Update semua BU/PICK yang belum `done` atau `cancel` - self.env['stock.picking'].search([ - ('sale_id', '=', pick.sale_id.id), - ('picking_type_id.code', '=', 'incoming'), - ('state', 'not in', ['done', 'cancel']) - ]).write({'linked_out_picking_id': latest_out_backorder.id}) - - return res + else: + pickings_not_to_do |= line.picking_id + + for pick_id in pickings_not_to_do: + moves_to_log = {} + for move in pick_id.move_lines: + if float_compare(move.product_uom_qty, + move.quantity_done, + precision_rounding=move.product_uom.rounding) > 0: + moves_to_log[move] = (move.quantity_done, move.product_uom_qty) + pick_id._log_less_quantities_than_expected(moves_to_log) + + pickings_to_validate = self.env.context.get('button_validate_picking_ids') + if pickings_to_validate: + pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate).with_context(skip_backorder=True) + if pickings_not_to_do: + pickings_to_validate = pickings_to_validate.with_context(picking_ids_not_to_backorder=pickings_not_to_do.ids) + return pickings_to_validate.button_validate() + return True diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index 4be0dff2..ec00df7b 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -5,25 +5,25 @@ class StockImmediateTransfer(models.TransientModel): _inherit = 'stock.immediate.transfer' def process(self): - """Override process method to add send_mail_bills logic.""" pickings_to_do = self.env['stock.picking'] pickings_not_to_do = self.env['stock.picking'] for line in self.immediate_transfer_line_ids: if line.to_immediate is True: + line.picking_id.send_mail_bills() + line.picking_id.send_koli_to_so() pickings_to_do |= line.picking_id else: pickings_not_to_do |= line.picking_id for picking in pickings_to_do: - picking.send_mail_bills() - # If still in draft => confirm and assign if picking.state == 'draft': picking.action_confirm() if picking.state != 'assigned': picking.action_assign() if picking.state != 'assigned': raise UserError(_("Could not reserve all requested products. Please use the 'Mark as Todo' button to handle the reservation manually.")) + for move in picking.move_lines.filtered(lambda m: m.state not in ['done', 'cancel']): for move_line in move.move_line_ids: move_line.qty_done = move_line.product_uom_qty @@ -33,4 +33,6 @@ class StockImmediateTransfer(models.TransientModel): pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate) pickings_to_validate = pickings_to_validate - pickings_not_to_do return pickings_to_validate.with_context(skip_immediate=True).button_validate() + return True + diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 02ce819f..f3af00d9 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -125,7 +125,6 @@ class StockPicking(models.Model): ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") notee = fields.Text(string="Note") quantity_koli = fields.Float(string="Quantity Koli", copy=False) - source_koli_id = fields.Many2one('stock.picking', string="Source Koli") @api.model def _compute_dokumen_tanda_terima(self): @@ -170,45 +169,17 @@ class StockPicking(models.Model): lalamove_image_url = fields.Char(string="Lalamove Image URL") lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html") - total_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli") total_koli = fields.Integer(compute='_compute_total_koli', string="Total Koli") total_koli_display = fields.Char(compute='_compute_total_koli_display', string="Total Koli Display") linked_out_picking_id = fields.Many2one('stock.picking', string="Linked BU/OUT", copy=False) - backorder_picking_id = fields.Many2one('stock.picking', string="Backorder Picking", copy=False) - - def action_create_backorder(self): - """ Override method to handle backorder logic automatically """ - backorder = super(StockPicking, self).action_create_backorder() - - for picking in self: - if 'BU/PICK/' in picking.name: - # Jika BU/PICK memiliki BU/OUT yang terhubung - if picking.linked_out_picking_id: - out_picking = picking.linked_out_picking_id - out_backorder = out_picking.backorder_picking_id - - # Jika BU/OUT belum punya backorder, hubungkan BU/PICK backorder ke BU/OUT lama - if not out_backorder: - backorder.linked_out_picking_id = out_picking - else: - # Jika BU/OUT sudah punya backorder, hubungkan ke backorder BU/OUT - backorder.linked_out_picking_id = out_backorder - - elif 'BU/OUT/' in picking.name: - # Jika BU/OUT membuat backorder, update semua BU/PICK yang terhubung ke BU/OUT lama - pickings_to_update = self.env['stock.picking'].search([('linked_out_picking_id', '=', picking.id)]) - for pick in pickings_to_update: - pick.linked_out_picking_id = backorder - - return backorder - + total_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli") - @api.depends('total_so_koli') # Sesuaikan dengan field yang relevan + @api.depends('total_so_koli') def _compute_total_so_koli(self): for picking in self: - picking.total_so_koli = self.env['check.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id)]) + picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')]) - @api.depends('total_koli') # Sesuaikan dengan field yang relevan + @api.depends('total_koli') def _compute_total_koli(self): for picking in self: picking.total_koli = self.env['scan.koli'].search_count([('picking_id', '=', picking.id)]) @@ -425,7 +396,7 @@ class StockPicking(models.Model): "name": order_line.product_id.name, "description": order_line.name, "value": order_line.price_unit, - "quantity": move_line.qty_done, # Menggunakan qty_done dari move_line + "quantity": move_line.qty_done, "weight": order_line.weight }) @@ -820,7 +791,7 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): - if self.total_koli != self.total_so_koli: + if self.total_koli > self.total_so_koli: raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") % (self.total_koli, self.total_so_koli)) if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': @@ -870,8 +841,7 @@ class StockPicking(models.Model): self.validation_minus_onhand_quantity() self.responsible = self.env.user.id - if self.picking_type_code == 'internal' and 'BU/PICK/' in self.name: - self.send_koli_to_so() + # self.send_koli_to_so() if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name: self.check_koli() res = super(StockPicking, self).button_validate() @@ -890,21 +860,29 @@ class StockPicking(models.Model): def send_koli_to_so(self): for picking in self: - for koli_line in picking.check_koli_lines: - existing_koli = self.env['sales.order.koli'].search([ - ('sale_order_id', '=', picking.sale_id.id), - ('picking_id', '=', picking.id), - ('koli_id', '=', koli_line.id) - ], limit=1) - - if not existing_koli: # Hindari duplikasi - self.env['sales.order.koli'].create({ - 'sale_order_id': picking.sale_id.id, - 'picking_id': picking.id, - 'koli_id': koli_line.id - }) - - + if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name: + for koli_line in picking.check_koli_lines: + existing_koli = self.env['sales.order.koli'].search([ + ('sale_order_id', '=', picking.sale_id.id), + ('picking_id', '=', picking.id), + ('koli_id', '=', koli_line.id) + ], limit=1) + + if not existing_koli: + self.env['sales.order.koli'].create({ + 'sale_order_id': picking.sale_id.id, + 'picking_id': picking.id, + 'koli_id': koli_line.id + }) + + if picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: + for koli_line in picking.scan_koli_lines: + existing_koli = self.env['sales.order.koli'].search([ + ('sale_order_id', '=', picking.sale_id.id), + ('koli_id', '=', koli_line.koli_id.koli_id.id) + ], limit=1) + + existing_koli.state = 'delivered' def check_qty_done_stock(self): for line in self.move_line_ids_without_package: @@ -981,62 +959,11 @@ class StockPicking(models.Model): res = super(StockPicking, self).action_cancel() return res - - def write(self, vals_list): - """ Override write method to auto-link BU/PICK to BU/OUT when necessary """ - records = super(StockPicking, self).write(vals_list) - for picking in records: - if 'BU/OUT/' in picking.name: - # Cari BU/PICK yang berhubungan berdasarkan logika tertentu - pick_picking = self.env['stock.picking'].search([ - ('name', 'like', 'BU/PICK/%'), - ('linked_out_picking_id', '=', False), - ('sale_id', '=', picking.sale_id.id) - ], limit=1) - - if pick_picking: - pick_picking.linked_out_picking_id = picking - - if 'BU/PICK/' in picking.name: - # Cari BU/PICK yang berhubungan berdasarkan logika tertentu - pick_picking = self.env['stock.picking'].search([ - ('name', 'like', 'BU/OUT/%'), - ('state', 'not in', ['cancel', 'done']), - ('sale_id', '=', picking.sale_id.id) - ], limit=1) - - if pick_picking: - pick_picking.linked_out_picking_id = picking - - return records - @api.model def create(self, vals): self._use_faktur(vals) - if vals.get('picking_type_code') == 'incoming' and vals.get('location_dest_id') == 58: - if 'name' in vals and vals['name'].startswith('BU/IN/'): - vals['name'] = vals['name'].replace('BU/IN/', 'BU/INPUT/', 1) - - if vals.get('picking_type_code') == 'internal' and vals.get('location_id') == 58: - if 'name' in vals and vals['name'].startswith('BU/INT'): - new_name = vals['name'].replace('BU/INT', 'BU/IN', 1) - # Periksa apakah nama sudah ada - if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', vals.get('company_id'))]) > 0: - new_name = f"{new_name}-DUP" - vals['name'] = new_name records = super(StockPicking, self).create(vals) - for picking in records: - if 'BU/OUT/' in picking.name: - # Cari BU/PICK yang berhubungan berdasarkan logika tertentu - pick_picking = self.env['stock.picking'].search([ - ('name', 'like', 'BU/PICK'), - ('linked_out_picking_id', '=', False), - ('origin', '=', picking.origin) - ], limit=1) - - if pick_picking: - pick_picking.linked_out_picking_id = picking return records @@ -1367,6 +1294,7 @@ class CheckKoli(models.Model): copy=False, ) koli = fields.Char(string='Koli') + reserved_id = fields.Many2one('stock.picking', string='Reserved Picking') class ScanKoli(models.Model): _name = 'scan.koli' @@ -1388,6 +1316,35 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + def unlink(self): + for scan in self: + koli = scan.koli_id.koli_id + if koli: + # Hapus reserved_id saat scan dihapus + koli.reserved_id = False + + # Ambil semua scan koli yang masih ada dan memiliki picking_id yang sama + remaining_scans = self.env['scan.koli'].search([ + ('id', '!=', scan.id), # Kecuali scan yang sedang dihapus + ('koli_id.picking_id', '=', koli.picking_id.id) + ]) + + # Jika tidak ada scan lain yang memiliki picking_id yang sama, hapus linked_out_picking_id + if not remaining_scans: + koli.picking_id.linked_out_picking_id = False + + return super(ScanKoli, self).unlink() + + @api.onchange('koli_id','scan_koli_progress') + def onchange_koli_id(self): + if not self.koli_id: + return + + for scan in self: + if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: + scan.koli_id.koli_id.reserved_id = scan.picking_id.id.origin + scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin + def _compute_scan_koli_progress(self): """ Menghitung progres scan koli dalam format 'X/Y' """ for scan in self: @@ -1397,18 +1354,12 @@ class ScanKoli(models.Model): total_so_koli = scan.picking_id.total_so_koli scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" - @api.model_create_multi - def create(self, vals_list): - """ Override create untuk update progress scan setelah scan koli ditambahkan """ - records = super(ScanKoli, self).create(vals_list) - for record in records: - record._compute_scan_koli_progress() - return records - @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): """ Validasi jika jumlah scan koli melebihi total SO koli """ for scan in self: + scan.koli_id.koli_id.reserved_id = scan.picking_id.id + scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id total_scans = len(scan.picking_id.scan_koli_lines) if total_scans > scan.picking_id.total_so_koli: raise UserError(_("Jumlah scan koli melebihi total SO koli!")) @@ -1418,9 +1369,20 @@ class ScanKoli(models.Model): if not self.koli_id: return - source_koli_so = self.picking_id.ids # Picking asal dari Koli yang dipilih - source_koli = self.koli_id.picking_id.linked_out_picking_id.ids - - # Cek apakah source_koli ditemukan + source_koli_so = self.picking_id.group_id.id + source_koli = self.koli_id.picking_id.group_id.id + if source_koli_so != source_koli: raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + + @api.onchange('koli_id') + def _onchange_koliii(self): + if self.koli_id and self.picking_id: + existing_koli = self.env['scan.koli'].search([ + ('picking_id', '=', self.picking_id.id), + ('koli_id', '=', self.koli_id.id), + ('id', '!=', self.id.origin) # Hindari validasi saat edit data + ]) + if existing_koli: + self.koli_id = False # Reset field koli_id agar pengguna tidak bisa memilihnya + raise UserError(f"Koli {existing_koli.koli_id.name} sudah dipindai dalam picking ini!") \ No newline at end of file -- cgit v1.2.3 From 0174a19b631d67a70805de45b252cdcf1c562fb6 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 10:28:15 +0700 Subject: push wms --- indoteknik_custom/models/stock_immediate_transfer.py | 6 ++++-- indoteknik_custom/models/stock_picking.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index ec00df7b..8724c567 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -9,14 +9,16 @@ class StockImmediateTransfer(models.TransientModel): pickings_not_to_do = self.env['stock.picking'] for line in self.immediate_transfer_line_ids: + line.picking_id.send_mail_bills() + line.picking_id.send_koli_to_so() if line.to_immediate is True: - line.picking_id.send_mail_bills() - line.picking_id.send_koli_to_so() pickings_to_do |= line.picking_id else: pickings_not_to_do |= line.picking_id for picking in pickings_to_do: + picking.send_mail_bills() + picking.send_koli_to_so() if picking.state == 'draft': picking.action_confirm() if picking.state != 'assigned': diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f3af00d9..f359a2fb 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -848,6 +848,7 @@ class StockPicking(models.Model): self.calculate_line_no() self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' + self.send_koli_to_so() return res -- cgit v1.2.3 From e94dbdf4418c686ec4e8fdab41d4f05e5284fbfb Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Feb 2025 13:41:50 +0700 Subject: push wms --- .../models/stock_immediate_transfer.py | 4 +- indoteknik_custom/models/stock_picking.py | 104 +++++++++++++-------- 2 files changed, 67 insertions(+), 41 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index 8724c567..35c17192 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -17,8 +17,8 @@ class StockImmediateTransfer(models.TransientModel): pickings_not_to_do |= line.picking_id for picking in pickings_to_do: - picking.send_mail_bills() - picking.send_koli_to_so() + # picking.send_mail_bills() + # picking.send_koli_to_so() if picking.state == 'draft': picking.action_confirm() if picking.state != 'assigned': diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f359a2fb..bbd9043d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -4,6 +4,7 @@ from odoo.tools.float_utils import float_is_zero from datetime import timedelta, datetime from itertools import groupby import pytz, requests, json, requests +from collections import defaultdict from dateutil import parser import datetime import hmac @@ -1318,72 +1319,97 @@ class ScanKoli(models.Model): ) def unlink(self): + picking_ids = set(self.mapped('picking_id.id')) # Tangkap picking_id sebelum hapus scan.koli + for scan in self: koli = scan.koli_id.koli_id if koli: - # Hapus reserved_id saat scan dihapus koli.reserved_id = False - # Ambil semua scan koli yang masih ada dan memiliki picking_id yang sama - remaining_scans = self.env['scan.koli'].search([ - ('id', '!=', scan.id), # Kecuali scan yang sedang dihapus + # Jika tidak ada scan.koli lain untuk picking_id yang sama, reset linked_out_picking_id + if not self.env['scan.koli'].search_count([ + ('id', '!=', scan.id), ('koli_id.picking_id', '=', koli.picking_id.id) - ]) - - # Jika tidak ada scan lain yang memiliki picking_id yang sama, hapus linked_out_picking_id - if not remaining_scans: + ]): koli.picking_id.linked_out_picking_id = False - return super(ScanKoli, self).unlink() + result = super(ScanKoli, self).unlink() # Hapus scan.koli + + # Reset qty_done jika semua scan.koli untuk picking_id tersebut telah dihapus + for picking_id in picking_ids: + self._reset_qty_done_if_no_scan(picking_id) + + return result - @api.onchange('koli_id','scan_koli_progress') + def _reset_qty_done_if_no_scan(self, picking_id): + """Set qty_done ke 0 hanya jika tidak ada scan.koli tersisa untuk picking_id tersebut.""" + remaining_scans = self.env['scan.koli'].search_count([('picking_id', '=', picking_id)]) + + if remaining_scans == 0: + picking = self.env['stock.picking'].browse(picking_id) + picking.move_line_ids_without_package.write({'qty_done': 0}) + picking.message_post(body=f"⚠️ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.") + + return remaining_scans + + @api.onchange('koli_id', 'scan_koli_progress') def onchange_koli_id(self): - if not self.koli_id: - return - - for scan in self: - if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: + for scan in self.filtered('koli_id'): + if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: scan.koli_id.koli_id.reserved_id = scan.picking_id.id.origin scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin + @api.depends('picking_id') def _compute_scan_koli_progress(self): - """ Menghitung progres scan koli dalam format 'X/Y' """ for scan in self: if scan.picking_id: - all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') - scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan total_so_koli = scan.picking_id.total_so_koli - scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" + scan.scan_koli_progress = f"{scan.picking_id.scan_koli_lines.ids.index(scan.id) + 1}/{total_so_koli}" if total_so_koli else "0/0" @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): - """ Validasi jika jumlah scan koli melebihi total SO koli """ for scan in self: scan.koli_id.koli_id.reserved_id = scan.picking_id.id scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id - total_scans = len(scan.picking_id.scan_koli_lines) - if total_scans > scan.picking_id.total_so_koli: - raise UserError(_("Jumlah scan koli melebihi total SO koli!")) + if len(scan.picking_id.scan_koli_lines) != scan.picking_id.total_so_koli: + raise UserError("Jumlah scan koli tidak sama dengan total SO koli!") @api.onchange('koli_id') def _onchange_koli_id(self): + for scan in self.filtered('koli_id'): + if scan.picking_id.group_id.id != scan.koli_id.picking_id.group_id.id: + raise UserError('Koli tidak sesuai, pastikan picking terkait benar!') + + @api.onchange('koli_id') + def _onchange_koliii(self): + for scan in self.filtered('koli_id'): + if self.env['scan.koli'].search_count([ + ('picking_id', '=', scan.picking_id.id), + ('koli_id', '=', scan.koli_id.id), + ('id', '!=', scan.id.origin) + ]): + scan.koli_id = False + raise UserError(f"Koli {scan.koli_id.name} sudah dipindai dalam picking ini!") + + @api.constrains('koli_id') + def _send_product_from_koli_id(self): if not self.koli_id: return - - source_koli_so = self.picking_id.group_id.id - source_koli = self.koli_id.picking_id.group_id.id - if source_koli_so != source_koli: - raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + koli_count_by_picking = defaultdict(int) + for scan in self: + koli_count_by_picking[scan.koli_id.picking_id.id] += 1 # Hitung jumlah koli per picking - @api.onchange('koli_id') - def _onchange_koliii(self): - if self.koli_id and self.picking_id: - existing_koli = self.env['scan.koli'].search([ - ('picking_id', '=', self.picking_id.id), - ('koli_id', '=', self.koli_id.id), - ('id', '!=', self.id.origin) # Hindari validasi saat edit data - ]) - if existing_koli: - self.koli_id = False # Reset field koli_id agar pengguna tidak bisa memilihnya - raise UserError(f"Koli {existing_koli.koli_id.name} sudah dipindai dalam picking ini!") \ No newline at end of file + for picking_id, total_koli in koli_count_by_picking.items(): + picking = self.env['stock.picking'].browse(picking_id) + + if total_koli == picking.quantity_koli: + # Ambil stock moves dari BU/PICK dan BU/OUT berdasarkan picking_id + pick_moves = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) + out_moves = self.env['stock.move.line'].search([('picking_id', '=', picking.linked_out_picking_id.id)]) + + # Sesuaikan product_id di BU/OUT dengan BU/PICK + for pick_move in pick_moves: + corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) + if corresponding_out_move: + corresponding_out_move.qty_done = corresponding_out_move.product_uom_qty # Update qty_done \ No newline at end of file -- cgit v1.2.3 From e9836504e2c814652165cd1a7055e91a8b6ea854 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 3 Mar 2025 13:15:17 +0700 Subject: CR customer commision jasper --- indoteknik_custom/models/commision.py | 65 +++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 6920154a..67cc5a62 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -1,4 +1,4 @@ -from odoo import models, api, fields +from odoo import models, api, fields, _ from odoo.exceptions import UserError from datetime import datetime import logging @@ -121,6 +121,21 @@ class CustomerRebate(models.Model): sum_dpp += invoice.price_subtotal return sum_dpp +class RejectReasonCommision(models.TransientModel): + _name = 'reject.reason.commision' + _description = 'Wizard for Reject Reason Customer Commision' + + request_id = fields.Many2one('customer.commision', string='Request') + reason_reject = fields.Text(string='Reason for Rejection', required=True, tracking=True) + + def confirm_reject(self): + commision = self.request_id + if commision: + commision.last_status = commision.status + commision.write({'reason_reject': self.reason_reject}) + commision.status = 'reject' + return {'type': 'ir.actions.act_window_close'} + class CustomerCommision(models.Model): _name = 'customer.commision' @@ -136,10 +151,23 @@ class CustomerCommision(models.Model): notification = fields.Char(string='Notification') commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', auto_join=True) status = fields.Selection([ + ('draft', 'Menunggu Approval Manager Sales'), + ('pengajuan1', 'Menunggu Approval Marketing'), + ('pengajuan2', 'Menunggu Approval Pimpinan'), + ('pengajuan3', 'Menunggu Approval Accounting'), + ('pengajuan4', 'Menunggu Approval Finnence'), + ('approved', 'Approved'), + ('reject', 'Rejected'), + ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange',default='draft') + last_status = fields.Selection([ + ('draft', 'Menunggu Approval Manager Sales'), ('pengajuan1', 'Menunggu Approval Marketing'), ('pengajuan2', 'Menunggu Approval Pimpinan'), - ('approved', 'Approved') - ], string='Status', copy=False, readonly=True, tracking=3) + ('pengajuan3', 'Menunggu Approval Accounting'), + ('pengajuan4', 'Menunggu Approval Finnence'), + ('approved', 'Approved'), + ('reject', 'Rejected'), + ], string='Status') commision_percent = fields.Float(string='Commision %', tracking=3) commision_amt = fields.Float(string='Commision Amount', tracking=3) total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp') @@ -148,15 +176,18 @@ class CustomerCommision(models.Model): ('cashback', 'Cashback'), ('rebate', 'Rebate'), ], string='Commision Type', required=True) - bank_name = fields.Char(string='Bank', tracking=3) - account_name = fields.Char(string='Account Name', tracking=3) - bank_account = fields.Char(string='Account No', tracking=3) + bank_name = fields.Char(string='Bank', tracking=3, required=True) + account_name = fields.Char(string='Account Name', tracking=3, required=True) + bank_account = fields.Char(string='Account No', tracking=3, required=True) note_transfer = fields.Char(string='Keterangan') brand_ids = fields.Many2many('x_manufactures', string='Brands') payment_status = fields.Selection([ ('pending', 'Pending'), ('payment', 'Payment'), ], string='Payment Status', copy=False, readonly=True, tracking=3, default='pending') + note_finnance = fields.Text('Notes Finnance') + reason_reject = fields.Char(string='Reason Reaject', tracking=True, track_visibility='onchange') + # add status for type of commision, fee, rebate / cashback # include child or not? @@ -220,11 +251,15 @@ class CustomerCommision(models.Model): return result def action_confirm_customer_commision(self):#add 2 step approval - if not self.status: + if not self.status or self.status == 'draft': self.status = 'pengajuan1' elif self.status == 'pengajuan1' and self.env.user.id == 19: self.status = 'pengajuan2' elif self.status == 'pengajuan2' and self.env.user.is_leader: + self.status = 'pengajuan3' + elif self.status == 'pengajuan3' and self.env.user.id == 1272: + self.status = 'pengajuan4' + elif self.status == 'pengajuan4' and self.env.user.id == 23: for line in self.commision_lines: line.invoice_id.is_customer_commision = True self.status = 'approved' @@ -232,6 +267,21 @@ class CustomerCommision(models.Model): raise UserError('Harus di approved oleh yang bersangkutan') return + def action_reject(self):#add 2 step approval + return { + 'type': 'ir.actions.act_window', + 'name': _('Reject Reason'), + 'res_model': 'reject.reason.commision', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_request_id': self.id}, + } + + def button_draft(self): + for commision in self: + commision.status = commision.last_status if commision.last_status else 'draft' + + def action_confirm_customer_payment(self): if self.status != 'approved': raise UserError('Commision harus di approve terlebih dahulu sebelum di konfirmasi pembayarannya') @@ -337,6 +387,7 @@ class CustomerCommisionLine(models.Model): total = fields.Float(string='Total') total_percent_margin = fields.Float('Total Margin', related='invoice_id.sale_id.total_percent_margin') product_id = fields.Many2one('product.product', string='Product') + sale_order_id = fields.Many2one('sale.order', string='Sale Order', related='invoice_id.sale_id') class AccountMove(models.Model): _inherit = 'account.move' -- cgit v1.2.3 From 20a56a76c519eba82f70ea1443e272ac64797dd9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 3 Mar 2025 16:51:05 +0700 Subject: push --- .../models/stock_backorder_confirmation.py | 2 +- indoteknik_custom/models/stock_picking.py | 195 +++++++++++++++------ 2 files changed, 141 insertions(+), 56 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_backorder_confirmation.py b/indoteknik_custom/models/stock_backorder_confirmation.py index f4da4cb5..d8a41f54 100644 --- a/indoteknik_custom/models/stock_backorder_confirmation.py +++ b/indoteknik_custom/models/stock_backorder_confirmation.py @@ -9,7 +9,7 @@ class StockBackorderConfirmation(models.TransientModel): pickings_not_to_do = self.env['stock.picking'] for line in self.backorder_confirmation_line_ids: line.picking_id.send_mail_bills() - line.picking_id.send_koli_to_so() + # line.picking_id.send_koli_to_so() if line.to_backorder is True: pickings_to_do |= line.picking_id else: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index bbd9043d..dfc33caa 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1,10 +1,10 @@ from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools.float_utils import float_is_zero +from collections import defaultdict from datetime import timedelta, datetime from itertools import groupby import pytz, requests, json, requests -from collections import defaultdict from dateutil import parser import datetime import hmac @@ -127,6 +127,8 @@ class StockPicking(models.Model): notee = fields.Text(string="Note") quantity_koli = fields.Float(string="Quantity Koli", copy=False) + + @api.model def _compute_dokumen_tanda_terima(self): for picking in self: @@ -178,7 +180,10 @@ class StockPicking(models.Model): @api.depends('total_so_koli') def _compute_total_so_koli(self): for picking in self: - picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')]) + if picking.state == 'done': + picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '=', 'delivered')]) + else: + picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')]) @api.depends('total_koli') def _compute_total_koli(self): @@ -190,6 +195,24 @@ class StockPicking(models.Model): for picking in self: picking.total_koli_display = f"{picking.total_koli} / {picking.total_so_koli}" + @api.constrains('quantity_koli') + def _constrains_quantity_koli(self): + for picking in self: + if not picking.linked_out_picking_id: + so_koli = self.env['sales.order.koli'].search([('picking_id', '=', picking.id)]) + + if so_koli: + so_koli.unlink() + + for rec in picking.check_koli_lines: + self.env['sales.order.koli'].create({ + 'sale_order_id': picking.sale_id.id, + 'picking_id': picking.id, + 'koli_id': rec.id, + }) + else: + raise UserError('Tidak Bisa Mengubah Quantity Koli Karena Koli Dari Picking Ini Sudah Dipakai Di BU/OUT!') + @api.onchange('quantity_koli') def _onchange_quantity_koli(self): self.check_koli_lines = [(5, 0, 0)] @@ -850,6 +873,31 @@ class StockPicking(models.Model): self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' self.send_koli_to_so() + if not self.env.context.get('skip_koli_check'): + for picking in self: + if picking.sale_id: + all_koli_ids = picking.sale_id.koli_lines.filtered(lambda k: k.state != 'delivered').ids + scanned_koli_ids = picking.scan_koli_lines.mapped('koli_id.id') + + missing_koli_ids = set(all_koli_ids) - set(scanned_koli_ids) + + if len(missing_koli_ids) > 0 and picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: + missing_koli_names = picking.sale_id.koli_lines.filtered(lambda k: k.id in missing_koli_ids and k.state != 'delivered').mapped('display_name') + missing_koli_list = "\n".join(f"- {name}" for name in missing_koli_names) + + # Buat wizard modal warning + wizard = self.env['warning.modal.wizard'].create({ + 'message': f"Berikut Koli yang belum discan:\n{missing_koli_list}", + 'picking_id': picking.id, + }) + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'warning.modal.wizard', + 'view_mode': 'form', + 'res_id': wizard.id, + 'target': 'new', + } return res @@ -876,15 +924,16 @@ class StockPicking(models.Model): 'picking_id': picking.id, 'koli_id': koli_line.id }) - + if picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: - for koli_line in picking.scan_koli_lines: - existing_koli = self.env['sales.order.koli'].search([ - ('sale_order_id', '=', picking.sale_id.id), - ('koli_id', '=', koli_line.koli_id.koli_id.id) - ], limit=1) - - existing_koli.state = 'delivered' + if picking.state == 'done': + for koli_line in picking.scan_koli_lines: + existing_koli = self.env['sales.order.koli'].search([ + ('sale_order_id', '=', picking.sale_id.id), + ('koli_id', '=', koli_line.koli_id.koli_id.id) + ], limit=1) + + existing_koli.state = 'delivered' def check_qty_done_stock(self): for line in self.move_line_ids_without_package: @@ -1319,78 +1368,83 @@ class ScanKoli(models.Model): ) def unlink(self): - picking_ids = set(self.mapped('picking_id.id')) # Tangkap picking_id sebelum hapus scan.koli - + picking_ids = set(self.mapped('koli_id.picking_id.id')) # Ambil semua picking_id yang terpengaruh for scan in self: koli = scan.koli_id.koli_id if koli: + # Hapus reserved_id saat scan dihapus koli.reserved_id = False - # Jika tidak ada scan.koli lain untuk picking_id yang sama, reset linked_out_picking_id - if not self.env['scan.koli'].search_count([ - ('id', '!=', scan.id), - ('koli_id.picking_id', '=', koli.picking_id.id) - ]): - koli.picking_id.linked_out_picking_id = False - - result = super(ScanKoli, self).unlink() # Hapus scan.koli - - # Reset qty_done jika semua scan.koli untuk picking_id tersebut telah dihapus + # Periksa ulang apakah masih ada scan.koli yang tersisa untuk setiap picking_id for picking_id in picking_ids: - self._reset_qty_done_if_no_scan(picking_id) + remaining_scans = self.env['sales.order.koli'].search_count([ + ('koli_id.picking_id', '=', picking_id) + ]) - return result + delete_koli = len(self.filtered(lambda rec: rec.koli_id.picking_id.id == picking_id)) - def _reset_qty_done_if_no_scan(self, picking_id): - """Set qty_done ke 0 hanya jika tidak ada scan.koli tersisa untuk picking_id tersebut.""" - remaining_scans = self.env['scan.koli'].search_count([('picking_id', '=', picking_id)]) - if remaining_scans == 0: - picking = self.env['stock.picking'].browse(picking_id) - picking.move_line_ids_without_package.write({'qty_done': 0}) - picking.message_post(body=f"⚠️ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.") + # Jika tidak ada scan.koli lain yang tersisa, set linked_out_picking_id ke False + if remaining_scans == delete_koli: + picking = self.env['stock.picking'].browse(picking_id) + picking.linked_out_picking_id = False + else: + raise UserError(_("Tidak dapat menghapus scan koli, karena masih ada scan koli lain yang tersisa untuk picking ini.")) + + for picking_id in picking_ids: + self._reset_qty_done_if_no_scan(picking_id) + + # self.check_koli_not_balance() - return remaining_scans + return super(ScanKoli, self).unlink() - @api.onchange('koli_id', 'scan_koli_progress') + @api.onchange('koli_id','scan_koli_progress') def onchange_koli_id(self): - for scan in self.filtered('koli_id'): - if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: + if not self.koli_id: + return + + for scan in self: + if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: scan.koli_id.koli_id.reserved_id = scan.picking_id.id.origin scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin - @api.depends('picking_id') def _compute_scan_koli_progress(self): + """ Menghitung progres scan koli dalam format 'X/Y' """ for scan in self: if scan.picking_id: + all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') + scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan total_so_koli = scan.picking_id.total_so_koli - scan.scan_koli_progress = f"{scan.picking_id.scan_koli_lines.ids.index(scan.id) + 1}/{total_so_koli}" if total_so_koli else "0/0" + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): - for scan in self: + """ Validasi jika jumlah scan koli melebihi total SO koli """ + for scan in self.picking_id.scan_koli_lines: scan.koli_id.koli_id.reserved_id = scan.picking_id.id scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id - if len(scan.picking_id.scan_koli_lines) != scan.picking_id.total_so_koli: - raise UserError("Jumlah scan koli tidak sama dengan total SO koli!") + + total_scans = len(self.picking_id.scan_koli_lines) + if total_scans != self.picking_id.total_so_koli: + raise UserError(_("Jumlah scan koli tidak sama dengan total SO koli!")) + + # def check_koli_not_balance(self): + # for scan in self: + # total_scancs = self.env['scan.koli'].search_count([('picking_id', '=', scan.picking_id.id), ('id', '!=', scan.id)]) + # if total_scancs != scan.picking_id.total_so_koli: + # raise UserError(_("Jumlah scan koli tidak sama dengan total SO koli!")) @api.onchange('koli_id') def _onchange_koli_id(self): - for scan in self.filtered('koli_id'): - if scan.picking_id.group_id.id != scan.koli_id.picking_id.group_id.id: - raise UserError('Koli tidak sesuai, pastikan picking terkait benar!') - - @api.onchange('koli_id') - def _onchange_koliii(self): - for scan in self.filtered('koli_id'): - if self.env['scan.koli'].search_count([ - ('picking_id', '=', scan.picking_id.id), - ('koli_id', '=', scan.koli_id.id), - ('id', '!=', scan.id.origin) - ]): - scan.koli_id = False - raise UserError(f"Koli {scan.koli_id.name} sudah dipindai dalam picking ini!") + if not self.koli_id: + return + source_koli_so = self.picking_id.group_id.id + source_koli = self.koli_id.picking_id.group_id.id + + if source_koli_so != source_koli: + raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + @api.constrains('koli_id') def _send_product_from_koli_id(self): if not self.koli_id: @@ -1412,4 +1466,35 @@ class ScanKoli(models.Model): for pick_move in pick_moves: corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) if corresponding_out_move: - corresponding_out_move.qty_done = corresponding_out_move.product_uom_qty # Update qty_done \ No newline at end of file + corresponding_out_move.qty_done += pick_move.qty_done + + def _reset_qty_done_if_no_scan(self, picking_id): + """Set qty_done ke 0 hanya jika tidak ada scan.koli tersisa untuk picking_id tersebut.""" + product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) + + for move in product_bu_pick: + product_bu_out = self.env['stock.move.line'].search([('picking_id', '=', self.picking_id.id), ('product_id', '=', move.product_id.id)]) + for bu_out in product_bu_out: + bu_out.qty_done -= move.qty_done + # if remaining_scans == 0: + # picking = self.env['stock.picking'].browse(picking_id) + # picking.move_line_ids_without_package.write({'qty_done': 0}) + # picking.message_post(body=f"⚠ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.") + + # return remaining_scans + +class WarningModalWizard(models.TransientModel): + _name = 'warning.modal.wizard' + _description = 'Peringatan Koli Belum Diperiksa' + + name = fields.Char(default="⚠️ Perhatian!") + message = fields.Text() + picking_id = fields.Many2one('stock.picking') + + def action_continue(self): + """Lanjutkan validasi setelah menutup wizard""" + if self.picking_id: + return self.picking_id.with_context(skip_koli_check=True).button_validate() + return {'type': 'ir.actions.act_window_close'} + + -- cgit v1.2.3 From 60de643d4a5f19abc7bee34ccd2e6e6f6219a750 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 4 Mar 2025 20:44:46 +0700 Subject: CR renca banner --- indoteknik_custom/models/solr/x_banner_banner.py | 3 ++- indoteknik_custom/models/x_banner_banner.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/x_banner_banner.py b/indoteknik_custom/models/solr/x_banner_banner.py index 8452644c..aa6e0c2a 100644 --- a/indoteknik_custom/models/solr/x_banner_banner.py +++ b/indoteknik_custom/models/solr/x_banner_banner.py @@ -23,7 +23,7 @@ class XBannerBanner(models.Model): 'function_name': function_name }) - @api.constrains('x_name', 'x_url_banner', 'background_color', 'x_banner_image', 'x_banner_category', 'x_relasi_manufacture', 'x_sequence_banner', 'x_status_banner', 'sequence', 'group_by_week', 'headline_banner_s', 'description_banner_s') + @api.constrains('x_name', 'x_url_banner', 'background_color', 'x_banner_image', 'x_banner_category', 'x_relasi_manufacture', 'x_sequence_banner', 'x_status_banner', 'sequence', 'group_by_week', 'headline_banner_s', 'description_banner_s', 'x_keyword_banner') def _create_solr_queue_sync_brands(self): self._create_solr_queue('_sync_banners_to_solr') @@ -51,6 +51,7 @@ class XBannerBanner(models.Model): 'group_by_week': banners.group_by_week or '', 'headline_banner_s': banners.x_headline_banner or '', 'description_banner_s': banners.x_description_banner or '', + 'keyword_banner_s': banners.x_keyword_banner or '', }) self.solr().add([document]) banners.update_last_update_solr() diff --git a/indoteknik_custom/models/x_banner_banner.py b/indoteknik_custom/models/x_banner_banner.py index 810bdf39..16d54b02 100755 --- a/indoteknik_custom/models/x_banner_banner.py +++ b/indoteknik_custom/models/x_banner_banner.py @@ -25,4 +25,5 @@ class XBannerBanner(models.Model): ('4', '4') ], string='Group by Week') x_headline_banner = fields.Text(string="Headline Banner") - x_description_banner = fields.Text(string="Description Banner") \ No newline at end of file + x_description_banner = fields.Text(string="Description Banner") + x_keyword_banner = fields.Text(string="Keyword Banner") \ No newline at end of file -- cgit v1.2.3 From 660913a45a1efe08f308d405e1011efc9744c553 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 5 Mar 2025 10:14:34 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index dfc33caa..df91d451 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -815,6 +815,9 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): + if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + if self.total_koli > self.total_so_koli: raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") % (self.total_koli, self.total_so_koli)) -- cgit v1.2.3 From ad19154ae49ec5bc1178006344baf104154167bf Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 6 Mar 2025 15:11:57 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 18 ++++++++++++++++++ indoteknik_custom/models/stock_picking_return.py | 13 ++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0699295f..327389cd 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -852,6 +852,12 @@ class StockPicking(models.Model): if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) + + if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: + raise UserError(_("Tidak ada koli! Harap periksa kembali.")) + if self.total_koli > self.total_so_koli: raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") % (self.total_koli, self.total_so_koli)) @@ -1405,6 +1411,18 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + @api.constrains('picking_id', 'koli_id') + def _check_duplicate_koli(self): + for record in self: + if record.koli_id: + existing_koli = self.search([ + ('picking_id', '=', record.picking_id.id), + ('koli_id', '=', record.koli_id.id), + ('id', '!=', record.id) # Exclude current record + ]) + if existing_koli: + raise ValidationError(f"⚠️ Koli '{record.koli_id.display_name}' sudah discan untuk picking ini!") + def unlink(self): picking_ids = set(self.mapped('koli_id.picking_id.id')) # Ambil semua picking_id yang terpengaruh for scan in self: diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index d4347235..a683d80e 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -24,4 +24,15 @@ class ReturnPicking(models.TransientModel): # if not stock_picking.approval_return_status == 'approved' and purchase.invoice_ids: # raise UserError('Harus Approval Accounting AP untuk melakukan Retur') - return res \ No newline at end of file + return res + +class ReturnPickingLine(models.TransientModel): + _inherit = 'stock.return.picking.line' + + @api.onchange('quantity') + def _onchange_quantity(self): + for rec in self: + qty_done = rec.move_id.quantity_done + + if rec.quantity > qty_done: + raise UserError(f"Quantity yang Anda masukkan tidak boleh melebihi quantity done yaitu: {qty_done}") \ No newline at end of file -- cgit v1.2.3 From ffcad6c52773063a05a91721b1203975a5a6359e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 10 Mar 2025 14:33:43 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 7b70c2b9..c5b6387d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -20,8 +20,6 @@ _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" - - class StockPicking(models.Model): _inherit = 'stock.picking' @@ -245,7 +243,6 @@ class StockPicking(models.Model): query = "SELECT update_sequance_stock_picking();" self.env.cr.execute(query) - @api.depends('estimated_ready_ship_date', 'state') def _callculate_sequance(self): for record in self: -- cgit v1.2.3 From 2b1783368e7be632e18be524b5288713125e7902 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 13 Mar 2025 11:25:22 +0700 Subject: uat bitehsip --- indoteknik_custom/models/sale_order.py | 34 ++++++++++++++----------------- indoteknik_custom/models/stock_picking.py | 17 ++++++++++++---- 2 files changed, 28 insertions(+), 23 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 852e3cf0..b22ee2ee 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -498,7 +498,6 @@ class SaleOrder(models.Model): if all_fast_products: return {'slatime': 1, 'include_instant': include_instant} - # Cari semua vendor pemenang untuk produk yang diberikan vendors = self.env['purchase.pricelist'].search([ ('product_id', 'in', product_ids), @@ -532,18 +531,18 @@ class SaleOrder(models.Model): max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) + + + current_date = datetime.now().date() if rec.date_order: - sum_days = max_slatime + self.get_days_until_next_business_day(rec.date_order) - 1 - if not rec.estimated_arrival_days: - rec.estimated_arrival_days = sum_days - - eta_date = rec.date_order + timedelta(days=sum_days) + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 + rec.estimated_arrival_days = sum_days + + eta_date = current_date + timedelta(days=sum_days) rec.estimated_ready_ship_date = eta_date rec.commitment_date = eta_date - # Jika expected_ready_to_ship kosong, set nilai default - if not rec.expected_ready_to_ship: - rec.expected_ready_to_ship = eta_date + rec.expected_ready_to_ship = eta_date @@ -765,14 +764,10 @@ class SaleOrder(models.Model): def write(self, vals): - res = super(SaleOrder, self).write(vals) - # self._compute_etrts_date() if 'carrier_id' in vals: for picking in self.picking_ids: if picking.state == 'assigned': picking.carrier_id = self.carrier_id - - return res def calculate_so_status(self): so_state = ['sale'] @@ -1229,7 +1224,7 @@ class SaleOrder(models.Model): order._set_sppkp_npwp_contact() order.calculate_line_no() order.send_notif_to_salesperson() - order._compute_etrts_date() + # order._compute_etrts_date() # order.order_line.get_reserved_from() res = super(SaleOrder, self).action_confirm() @@ -1629,15 +1624,15 @@ class SaleOrder(models.Model): # order._update_partner_details() return order - def write(self, vals): + # def write(self, vals): # Call the super method to handle the write operation - res = super(SaleOrder, self).write(vals) + # res = super(SaleOrder, self).write(vals) # self._compute_etrts_date() # Check if the update is coming from a save operation # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']): # self._update_partner_details() - return res + # return res def _update_partner_details(self): for order in self: @@ -1666,7 +1661,8 @@ class SaleOrder(models.Model): if command[0] == 0: # A new line is being added raise UserError( "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") + res = super(SaleOrder, self).write(vals) - if 'order_line' in vals: - self._compute_etrts_date() + if any(field in vals for field in ["order_line", "client_order_ref"]): + self._compute_etrts_date() return res \ No newline at end of file diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ab8109c7..edc9cc78 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -13,11 +13,11 @@ import requests import time import logging import re -from deep_translator import GoogleTranslator _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" -_biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" +_biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" +# _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" @@ -492,8 +492,18 @@ class StockPicking(models.Model): self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "") self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "") self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "") + + waybill_id = data.get("courier", {}).get("waybill_id", "") + + message = f"✅ Berhasil Order ke Biteship! Resi: {waybill_id}" if waybill_id else "⚠️ Order berhasil, tetapi tidak ada nomor resi." - return data + return { + 'effect': { + 'fadeout': 'slow', # Efek menghilang perlahan + 'message': message, # Pesan sukses + 'type': 'rainbow_man', # Efek animasi lucu Odoo + } + } else: error_data = response.json() error_message = error_data.get("error", "Unknown error") @@ -1203,7 +1213,6 @@ class StockPicking(models.Model): manifests.append({ "status": re.sub(r'[^a-zA-Z0-9\s]', ' ', entry["status"]).lower().capitalize(), "datetime": self._convert_to_local_time(entry["updated_at"]), - # "description": GoogleTranslator(source='auto', target='id').translate(entry["note"]), "description": description[entry["status"]], }) -- cgit v1.2.3 From 29a64fa64c72c4cb263806b099775a18124a5da7 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 14 Mar 2025 08:34:52 +0700 Subject: cr md bom --- indoteknik_custom/models/sale_order.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 14a8e688..b311abda 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1,3 +1,5 @@ +from re import search + from odoo import fields, models, api, _ from odoo.exceptions import UserError, ValidationError from datetime import datetime, timedelta @@ -978,8 +980,20 @@ class SaleOrder(models.Model): raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") if not order.user_id.active: raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact") - + + def check_product_bom(self): + for order in self: + for line in order.order_line: + if 'bom' in line.product_id.default_code: + search_bom = self.env['mrp.production'].search([('product_id', '=', line.id)], limit=1,) + if not search_bom: + raise UserError("Product BOM belum ada di manufacturing orders, silahkan hubungi MD") + else: + if search_bom.state != 'confirmed': + raise UserError("Product BOM belum di confirm di manufacturing orders, silahkan hubungi MD") + def sale_order_approve(self): + self.check_product_bom() self.check_credit_limit() self.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: -- cgit v1.2.3 From 9acebb424ead07109438e46c4f96038c9f50fbec Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 14 Mar 2025 09:36:51 +0700 Subject: cr calculate Estimated Ready To Ship Date --- indoteknik_custom/models/sale_order.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 14a8e688..01aefe7a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -206,8 +206,7 @@ class SaleOrder(models.Model): ) expected_ready_to_ship = fields.Datetime( string='ET Ready to Ship', - copy=False, - store=True + copy=False ) shipping_method_picking = fields.Char(string='Shipping Method Picking', compute='_compute_shipping_method_picking') @@ -555,7 +554,7 @@ class SaleOrder(models.Model): return {'slatime': max_slatime, 'include_instant': include_instant} - @api.depends("order_line.product_id") + # @api.depends("order_line.product_id") def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date for rec in self: max_slatime = 1 # Default SLA jika tidak ada @@ -1258,7 +1257,7 @@ class SaleOrder(models.Model): order._set_sppkp_npwp_contact() order.calculate_line_no() order.send_notif_to_salesperson() - order._compute_etrts_date() + # order._compute_etrts_date() # order.order_line.get_reserved_from() res = super(SaleOrder, self).action_confirm() -- cgit v1.2.3 From 72bf5f78f3503f2a14c3a329653d0726a14d93c8 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 14 Mar 2025 10:08:50 +0700 Subject: update code --- indoteknik_custom/models/sale_order.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b311abda..aed0c1df 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -984,14 +984,14 @@ class SaleOrder(models.Model): def check_product_bom(self): for order in self: for line in order.order_line: - if 'bom' in line.product_id.default_code: - search_bom = self.env['mrp.production'].search([('product_id', '=', line.id)], limit=1,) - if not search_bom: - raise UserError("Product BOM belum ada di manufacturing orders, silahkan hubungi MD") + if 'bom' in line.product_id.default_code.lower() or 'bom-it' in line.name.lower(): + search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)],order='name desc') + if search_bom: + confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') + if not confirmed_bom: + raise UserError("Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") else: - if search_bom.state != 'confirmed': - raise UserError("Product BOM belum di confirm di manufacturing orders, silahkan hubungi MD") - + raise UserError("Product BOM belum di confirm di manufacturing orders, silahkan hubungi MD") def sale_order_approve(self): self.check_product_bom() self.check_credit_limit() -- cgit v1.2.3 From cba69ee6c06c3386bed68b537c56393ae2cba11f Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 17 Mar 2025 09:01:52 +0700 Subject: fix code bom --- indoteknik_custom/models/sale_order.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index aed0c1df..adc9bb98 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -991,7 +991,7 @@ class SaleOrder(models.Model): if not confirmed_bom: raise UserError("Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") else: - raise UserError("Product BOM belum di confirm di manufacturing orders, silahkan hubungi MD") + raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD") def sale_order_approve(self): self.check_product_bom() self.check_credit_limit() @@ -1053,6 +1053,7 @@ class SaleOrder(models.Model): order.approval_status = 'pengajuan2' return self._create_approval_notification('Pimpinan') elif order._requires_approval_margin_manager(): + self.check_product_bom() self.check_credit_limit() self.check_limit_so_to_invoice() order.approval_status = 'pengajuan1' @@ -1232,6 +1233,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: + order.check_product_bom() order.check_credit_limit() order.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: -- cgit v1.2.3 From e4d86ee7cb2fac5c09876b1aeefda04f27ebedd0 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 17 Mar 2025 09:03:51 +0700 Subject: biteship handle sla SO --- indoteknik_custom/models/sale_order.py | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b22ee2ee..7ccc551b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -450,10 +450,11 @@ class SaleOrder(models.Model): @api.depends('date_order', 'estimated_arrival_days', 'state', 'estimated_arrival_days_start') def _compute_eta_date(self): + current_date = datetime.now().date() for rec in self: if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start: - rec.eta_date = rec.date_order + timedelta(days=rec.estimated_arrival_days) - rec.eta_date_start = rec.date_order + timedelta(days=rec.estimated_arrival_days_start) + rec.eta_date = current_date + timedelta(days=rec.estimated_arrival_days) + rec.eta_date_start = current_date + timedelta(days=rec.estimated_arrival_days_start) else: rec.eta_date = False rec.eta_date_start = False @@ -527,22 +528,23 @@ class SaleOrder(models.Model): @api.depends("order_line.product_id") def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date - for rec in self: - max_slatime = 1 # Default SLA jika tidak ada - slatime = self.calculate_sla_by_vendor(rec.order_line) - max_slatime = max(max_slatime, slatime['slatime']) - - - current_date = datetime.now().date() - - if rec.date_order: - sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 - rec.estimated_arrival_days = sum_days + if self.order_line: + for rec in self: + max_slatime = 1 # Default SLA jika tidak ada + slatime = self.calculate_sla_by_vendor(rec.order_line) + max_slatime = max(max_slatime, slatime['slatime']) - eta_date = current_date + timedelta(days=sum_days) - rec.estimated_ready_ship_date = eta_date - rec.commitment_date = eta_date - rec.expected_ready_to_ship = eta_date + current_date = datetime.now().date() + + if rec.date_order: + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 + if rec.source_id.name != 'Website': + rec.estimated_arrival_days = sum_days + + eta_date = current_date + timedelta(days=sum_days) + rec.estimated_ready_ship_date = eta_date + rec.commitment_date = eta_date + rec.expected_ready_to_ship = eta_date -- cgit v1.2.3 From c263662a9dd388eb9b23ae6ab8d9cd3f2bffb3f8 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 18 Mar 2025 09:22:54 +0700 Subject: push --- indoteknik_custom/models/commision.py | 36 ++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 67cc5a62..a5518c0a 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -152,19 +152,21 @@ class CustomerCommision(models.Model): commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', auto_join=True) status = fields.Selection([ ('draft', 'Menunggu Approval Manager Sales'), - ('pengajuan1', 'Menunggu Approval Marketing'), - ('pengajuan2', 'Menunggu Approval Pimpinan'), - ('pengajuan3', 'Menunggu Approval Accounting'), - ('pengajuan4', 'Menunggu Approval Finnence'), + ('pengajuan1', 'Menunggu Approval Sales'), + ('pengajuan2', 'Menunggu Approval Marketing'), + ('pengajuan3', 'Menunggu Approval Pimpinan'), + ('pengajuan4', 'Menunggu Approval Accounting'), + ('pengajuan5', 'Menunggu Approval Finance'), ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange',default='draft') last_status = fields.Selection([ ('draft', 'Menunggu Approval Manager Sales'), - ('pengajuan1', 'Menunggu Approval Marketing'), - ('pengajuan2', 'Menunggu Approval Pimpinan'), - ('pengajuan3', 'Menunggu Approval Accounting'), - ('pengajuan4', 'Menunggu Approval Finnence'), + ('pengajuan1', 'Menunggu Approval Sales'), + ('pengajuan2', 'Menunggu Approval Marketing'), + ('pengajuan3', 'Menunggu Approval Pimpinan'), + ('pengajuan4', 'Menunggu Approval Accounting'), + ('pengajuan5', 'Menunggu Approval Finance'), ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status') @@ -187,6 +189,7 @@ class CustomerCommision(models.Model): ], string='Payment Status', copy=False, readonly=True, tracking=3, default='pending') note_finnance = fields.Text('Notes Finnance') reason_reject = fields.Char(string='Reason Reaject', tracking=True, track_visibility='onchange') + approved_by = fields.Char(string='Approved By', tracking=True, track_visibility='always') # add status for type of commision, fee, rebate / cashback @@ -250,19 +253,26 @@ class CustomerCommision(models.Model): result = super(CustomerCommision, self).create(vals) return result - def action_confirm_customer_commision(self):#add 2 step approval + def action_confirm_customer_commision(self): if not self.status or self.status == 'draft': self.status = 'pengajuan1' - elif self.status == 'pengajuan1' and self.env.user.id == 19: + elif self.status == 'pengajuan1' and self.env.user.is_sales_manager: self.status = 'pengajuan2' - elif self.status == 'pengajuan2' and self.env.user.is_leader: + self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + elif self.status == 'pengajuan2' and self.env.user.id == 19: self.status = 'pengajuan3' - elif self.status == 'pengajuan3' and self.env.user.id == 1272: + self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + elif self.status == 'pengajuan3' and self.env.user.is_leader: self.status = 'pengajuan4' - elif self.status == 'pengajuan4' and self.env.user.id == 23: + self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + elif self.status == 'pengajuan4' and self.env.user.id == 1272: + self.status = 'pengajuan5' + self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + elif self.status == 'pengajuan5' and self.env.user.id == 23: for line in self.commision_lines: line.invoice_id.is_customer_commision = True self.status = 'approved' + self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name else: raise UserError('Harus di approved oleh yang bersangkutan') return -- cgit v1.2.3 From 0f962b64b4c62e236d6096c594d7fbe8c49276d8 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 11:50:08 +0700 Subject: md selish --- indoteknik_custom/models/stock_picking.py | 40 ++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ab8109c7..1e93da80 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -131,6 +131,16 @@ class StockPicking(models.Model): ('cancel', 'Cancelled'), ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note") + state_approve_md = fields.Selection([ + ('waiting', 'Waiting For Approve by MD'), + ('pending', 'Pending (cari dulu barangnya)'), + ('done', 'Approve by MD'), + ], string='Approval MD Gudang Selisih', tracking=True, copy=False, help="The current state of the MD Approval transfer barang from gudang selisih.") + show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") + + def _compute_show_state_approve_md(self): + for record in self: + record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" @api.model def _compute_dokumen_tanda_terima(self): @@ -881,8 +891,36 @@ class StockPicking(models.Model): qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) if line.qty_done > qty_onhand: raise UserError('Quantity Done melebihi Quantity Onhand') + def button_state_approve_md(self): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.env.user.id in users_in_group.mapped('id'): + self.state_approve_md = 'done' + else: + raise UserError('Hanya MD yang bisa Approve') + + def button_state_pending_md(self): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.env.user.id in users_in_group.mapped('id'): + self.state_approve_md = 'pending' + else: + raise UserError('Hanya MD yang bisa Approve') def button_validate(self): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.location_id.id == 47 and self.env.user.id not in users_in_group.mapped('id') and self.state_approve_md != 'done': + self.state_approve_md = 'waiting' if self.state_approve_md != 'pending' else 'pending' + self.env.cr.commit() + raise UserError("Transfer dari gudang selisih harus di approve MD, Hubungi MD agar bisa di Validate") + else: + if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): + self.state_approve_md = 'done' + if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") @@ -907,7 +945,7 @@ class StockPicking(models.Model): if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: raise UserError("Harus di Approve oleh Logistik") - + if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager: raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara") -- cgit v1.2.3 From 3f456ca27eaf98e5396da75f18e8106688491a46 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 11:53:55 +0700 Subject: update chek product bom --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4d186c8d..9d7be55a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -983,7 +983,7 @@ class SaleOrder(models.Model): def check_product_bom(self): for order in self: for line in order.order_line: - if 'bom' in line.product_id.default_code.lower() or 'bom-it' in line.name.lower(): + if 'bom-it' in line.name or 'bom' in line.product_id.default_code: search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)],order='name desc') if search_bom: confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') -- cgit v1.2.3 From 4b3c012f617683cdcb85251ac2da30d40ea4e093 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 13:04:17 +0700 Subject: fix code --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 9d7be55a..4d632c71 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -983,7 +983,7 @@ class SaleOrder(models.Model): def check_product_bom(self): for order in self: for line in order.order_line: - if 'bom-it' in line.name or 'bom' in line.product_id.default_code: + if 'bom-it' in line.product_id.name or 'bom' in line.product_id.default_code if line.product_id.default_code else False: search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)],order='name desc') if search_bom: confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') -- cgit v1.2.3 From a06059e47feab3aa25c35652dfb83b8783273084 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 13:14:12 +0700 Subject: fix code check bom-it --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4d632c71..67434105 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -983,7 +983,7 @@ class SaleOrder(models.Model): def check_product_bom(self): for order in self: for line in order.order_line: - if 'bom-it' in line.product_id.name or 'bom' in line.product_id.default_code if line.product_id.default_code else False: + if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False: search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)],order='name desc') if search_bom: confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') -- cgit v1.2.3 From 2b34ec96718d693d7a322939b23bda2fe64d8c80 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 18 Mar 2025 15:25:34 +0700 Subject: push --- indoteknik_custom/models/commision.py | 24 +++++++++++++++++++----- indoteknik_custom/models/stock_picking.py | 2 ++ 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 62545984..d6b170d2 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -2,6 +2,7 @@ from odoo import models, api, fields, _ from odoo.exceptions import UserError from datetime import datetime import logging +from terbilang import Terbilang _logger = logging.getLogger(__name__) @@ -151,8 +152,7 @@ class CustomerCommision(models.Model): notification = fields.Char(string='Notification') commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', auto_join=True) status = fields.Selection([ - ('draft', 'Menunggu Approval Manager Sales'), - ('pengajuan1', 'Menunggu Approval Sales'), + ('pengajuan1', 'Menunggu Approval Manager Sales'), ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), @@ -161,8 +161,7 @@ class CustomerCommision(models.Model): ('reject', 'Rejected'), ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange',default='draft') last_status = fields.Selection([ - ('draft', 'Menunggu Approval Manager Sales'), - ('pengajuan1', 'Menunggu Approval Sales'), + ('pengajuan1', 'Menunggu Approval Manager Sales'), ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), @@ -172,6 +171,7 @@ class CustomerCommision(models.Model): ], string='Status') commision_percent = fields.Float(string='Commision %', tracking=3) commision_amt = fields.Float(string='Commision Amount', tracking=3) + commision_amt_text = fields.Char(string='Commision Amount Text', compute='compute_delivery_amt_text') total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp') commision_type = fields.Selection([ ('fee', 'Fee'), @@ -194,13 +194,27 @@ class CustomerCommision(models.Model): grouped_so_number = fields.Char(string='Group SO Number', compute='_compute_grouped_numbers') grouped_invoice_number = fields.Char(string='Group Invoice Number', compute='_compute_grouped_numbers') + def compute_delivery_amt_text(self): + tb = Terbilang() + + for record in self: + res = '' + + try: + if record.commision_amt > 0: + tb.parse(int(record.commision_amt)) + res = tb.getresult().title() + record.commision_amt_text = res + ' Rupiah' + except: + record.commision_amt_text = res + def _compute_grouped_numbers(self): for rec in self: so_numbers = set() invoice_numbers = set() for line in rec.commision_lines: - if line.invoice_id: + if line.invoice_id: if line.invoice_id.sale_id: so_numbers.add(line.invoice_id.sale_id.name) invoice_numbers.add(line.invoice_id.name) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ab8109c7..fe8557c3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1011,6 +1011,8 @@ class StockPicking(models.Model): if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") + + res = super(StockPicking, self).action_cancel() return res -- cgit v1.2.3 From e7a1a6a1fbbc7e74291471d2abc9487511a8a861 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 15:39:13 +0700 Subject: fix code md gudang selisih --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1e93da80..9ea9f2a2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -133,7 +133,7 @@ class StockPicking(models.Model): notee = fields.Text(string="Note") state_approve_md = fields.Selection([ ('waiting', 'Waiting For Approve by MD'), - ('pending', 'Pending (cari dulu barangnya)'), + ('pending', 'Pending (perlu koordinasi dengan MD)'), ('done', 'Approve by MD'), ], string='Approval MD Gudang Selisih', tracking=True, copy=False, help="The current state of the MD Approval transfer barang from gudang selisih.") show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") -- cgit v1.2.3 From 4a7b5ebc82de37c6d2bde5e670066336256939d5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Mar 2025 09:55:02 +0700 Subject: cr reklas uang muka and permission button cancel stock picking --- indoteknik_custom/models/invoice_reklas.py | 6 ++++++ indoteknik_custom/models/stock_picking.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/invoice_reklas.py b/indoteknik_custom/models/invoice_reklas.py index f5bb5a25..d10d4c31 100644 --- a/indoteknik_custom/models/invoice_reklas.py +++ b/indoteknik_custom/models/invoice_reklas.py @@ -18,6 +18,12 @@ class InvoiceReklas(models.TransientModel): ('pembelian', 'Pembelian'), ], string='Reklas Tipe') + @api.onchange('reklas_type') + def _onchange_reklas_type(self): + if self.reklas_type == 'penjualan': + invoices = self.env['account.move'].browse(self._context.get('active_ids', [])) + self.pay_amt = invoices.amount_total + def create_reklas(self): if not self.reklas_type: raise UserError('Reklas Tipe harus diisi') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ab8109c7..4229d33e 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1008,9 +1008,12 @@ class StockPicking(models.Model): return True def action_cancel(self): - if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': + if not self.env.user.is_logistic_approver: if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") + + if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic'): + raise UserError("Button ini hanya untuk Logistik") res = super(StockPicking, self).action_cancel() return res -- cgit v1.2.3 From fc5defa647bcdd317dc2d4069432c2dcc1141344 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 19 Mar 2025 10:04:31 +0700 Subject: change mthode validation expected date --- indoteknik_custom/models/sale_order.py | 87 ++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 42 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 7ccc551b..e2755eba 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -190,10 +190,10 @@ class SaleOrder(models.Model): ('PNR', 'Pareto Non Repeating'), ('NP', 'Non Pareto') ]) - estimated_ready_ship_date = fields.Datetime( - string='ET Ready to Ship compute', - compute='_compute_etrts_date' - ) + # estimated_ready_ship_date = fields.Datetime( + # string='ET Ready to Ship compute', + # compute='_compute_etrts_date' + # ) expected_ready_to_ship = fields.Datetime( string='ET Ready to Ship', copy=False, @@ -479,16 +479,6 @@ class SaleOrder(models.Model): break return offset - - # def calculate_sla_by_vendor(self, products): - # slatime = 15 - # for line in products: - # product_sla = self.env['product.sla'].search([('product_variant_id', '=', line.product_id.id)], limit=1) - # slatime = int(product_sla.sla) if product_sla and product_sla.sla and product_sla.sla != 'Indent' and "hari" in product_sla.sla.lower() else 15 - - # return { - # 'slatime' : slatime - # } def calculate_sla_by_vendor(self, products): product_ids = products.mapped('product_id.id') # Kumpulkan semua ID produk @@ -526,43 +516,55 @@ class SaleOrder(models.Model): return {'slatime': max_slatime, 'include_instant': include_instant} - @api.depends("order_line.product_id") + @api.depends("order_line.product_id", "date_order") def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date - if self.order_line: - for rec in self: - max_slatime = 1 # Default SLA jika tidak ada - slatime = self.calculate_sla_by_vendor(rec.order_line) - max_slatime = max(max_slatime, slatime['slatime']) - - current_date = datetime.now().date() + for rec in self: + if not rec.date_order: + rec.expected_ready_to_ship = False + return + + current_date = datetime.now().date() + + max_slatime = 1 # Default SLA jika tidak ada + slatime = self.calculate_sla_by_vendor(rec.order_line) + max_slatime = max(max_slatime, slatime['slatime']) - if rec.date_order: - sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 - if rec.source_id.name != 'Website': - rec.estimated_arrival_days = sum_days - - eta_date = current_date + timedelta(days=sum_days) - rec.estimated_ready_ship_date = eta_date - rec.commitment_date = eta_date - rec.expected_ready_to_ship = eta_date - - - - @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship - def _onchange_expected_ready_ship_date(self): + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 + if not rec.estimated_arrival_days: + rec.estimated_arrival_days = sum_days + + eta_date = current_date + timedelta(days=sum_days) + rec.commitment_date = eta_date + # Jika expected_ready_to_ship kosong, set nilai default + if not rec.expected_ready_to_ship: + rec.expected_ready_to_ship = eta_date + + def _validate_expected_ready_ship_date(self): for rec in self: - if rec.expected_ready_to_ship and rec.estimated_ready_ship_date: + if rec.expected_ready_to_ship and rec.commitment_date: + current_date = datetime.now().date() # Hanya membandingkan tanggal saja, tanpa jam expected_date = rec.expected_ready_to_ship.date() - estimated_date = rec.estimated_ready_ship_date.date() - if expected_date < estimated_date: - rec.expected_ready_to_ship = rec.estimated_ready_ship_date - rec.commitment_date = rec.estimated_ready_ship_date + max_slatime = 1 # Default SLA jika tidak ada + slatime = self.calculate_sla_by_vendor(rec.order_line) + max_slatime = max(max_slatime, slatime['slatime']) + sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 + eta_minimum = current_date + timedelta(days=sum_days) + + if expected_date < eta_minimum: + rec.expected_ready_to_ship = eta_minimum raise ValidationError( "Tanggal 'Expected Ready to Ship' tidak boleh lebih kecil dari {}. Mohon pilih tanggal minimal {}." - .format(estimated_date.strftime('%d-%m-%Y'), estimated_date.strftime('%d-%m-%Y')) + .format(eta_minimum.strftime('%d-%m-%Y'), eta_minimum.strftime('%d-%m-%Y')) ) + else: + rec.commitment_date = rec.expected_ready_to_ship + + + @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship + def _onchange_expected_ready_ship_date(self): + self._validate_expected_ready_ship_date() def _set_etrts_date(self): for order in self: @@ -1623,6 +1625,7 @@ class SaleOrder(models.Model): # Ensure partner details are updated when a sale order is created order = super(SaleOrder, self).create(vals) order._compute_etrts_date() + order._validate_expected_ready_ship_date() # order._update_partner_details() return order -- cgit v1.2.3 From 4c8ff729c027654870b3cf71015e5c7ecec28a7b Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 19 Mar 2025 10:32:24 +0700 Subject: expected rts --- indoteknik_custom/models/sale_order.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index acad7729..b17df045 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -545,9 +545,8 @@ class SaleOrder(models.Model): max_slatime = max(max_slatime, slatime) return {'slatime': max_slatime, 'include_instant': include_instant} - - @api.depends("order_line.product_id", "date_order") - def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date + + def _calculate_etrts_date(self): for rec in self: if not rec.date_order: rec.expected_ready_to_ship = False @@ -565,9 +564,12 @@ class SaleOrder(models.Model): eta_date = current_date + timedelta(days=sum_days) rec.commitment_date = eta_date - # Jika expected_ready_to_ship kosong, set nilai default - if not rec.expected_ready_to_ship: - rec.expected_ready_to_ship = eta_date + rec.expected_ready_to_ship = eta_date + + @api.depends("order_line.product_id", "date_order") + def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date + self._calculate_etrts_date() + def _validate_expected_ready_ship_date(self): for rec in self: @@ -1713,5 +1715,5 @@ class SaleOrder(models.Model): res = super(SaleOrder, self).write(vals) if any(field in vals for field in ["order_line", "client_order_ref"]): - self._compute_etrts_date() + self._calculate_etrts_date() return res \ No newline at end of file -- cgit v1.2.3 From 1f324148f176bafc471a5948b8c5322a9b175ffa Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 19 Mar 2025 12:49:44 +0700 Subject: request iman --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 9e5fca66..23ddb47f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -136,11 +136,11 @@ class StockPicking(models.Model): ('pending', 'Pending (perlu koordinasi dengan MD)'), ('done', 'Approve by MD'), ], string='Approval MD Gudang Selisih', tracking=True, copy=False, help="The current state of the MD Approval transfer barang from gudang selisih.") - show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") + # show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") - def _compute_show_state_approve_md(self): - for record in self: - record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" + # def _compute_show_state_approve_md(self): + # for record in self: + # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" @api.model def _compute_dokumen_tanda_terima(self): -- cgit v1.2.3 From 99cf4f8d2a109f09049a52bccdb85dde6aec1081 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Mar 2025 13:17:52 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 23ddb47f..b8a83d5c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1056,7 +1056,7 @@ class StockPicking(models.Model): return True def action_cancel(self): - if not self.env.user.is_logistic_approver: + if not self.env.user.is_logistic_approver and (self.env.context.get('active_model') == 'stock.picking' or self.env.context.get('active_model') == 'stock.picking.type'): if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") -- cgit v1.2.3 From cc1759574f76b084a1ce44e1acf01ed20dcdd729 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Mar 2025 13:34:25 +0700 Subject: fix bug button cancel stock picking --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b8a83d5c..6c6cbaa1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1060,7 +1060,7 @@ class StockPicking(models.Model): if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") - if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic'): + if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing': raise UserError("Button ini hanya untuk Logistik") res = super(StockPicking, self).action_cancel() -- cgit v1.2.3 From c48f3204c84cf7fca8da827178c4971370b324f8 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 20 Mar 2025 10:16:37 +0700 Subject: update logic duplicatee contact --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index abcb6f2f..caa420e5 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -548,20 +548,7 @@ class UserPengajuanTempoRequest(models.Model): ('name', '=', contact_data['name']) ], limit=1) - if existing_contact: - # Pastikan tidak ada duplikasi nama dalam perusahaan yang sama - duplicate_check = self.env['res.partner'].search([ - ('name', '=', contact_data['name']), - ('id', '!=', existing_contact.id) # Hindari update yang menyebabkan duplikasi global - ], limit=1) - - if not duplicate_check: - # Perbarui hanya field yang tidak menyebabkan konflik - update_data = {k: v for k, v in contact_data.items() if k != 'name'} - existing_contact.write(update_data) - else: - raise UserError(f"Skipping update for {contact_data['name']} due to existing duplicate.") - else: + if not existing_contact: # Pastikan tidak ada partner lain dengan nama yang sama sebelum membuat baru duplicate_check = self.env['res.partner'].search([ ('name', '=', contact_data['name']) -- cgit v1.2.3 From 3ed91948307260a25efae332c6dae013d276fef5 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 20 Mar 2025 15:05:01 +0700 Subject: add sale order post massage when confirm product bom --- indoteknik_custom/models/mrp_production.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 54d90256..0bf98702 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -6,5 +6,18 @@ class MrpProduction(models.Model): _inherit = 'mrp.production' desc = fields.Text(string='Description') - - \ No newline at end of file + sale_order = fields.Many2one('sale.order', string='Sale Order', required=True, copy=False) + + def action_confirm(self): + """Override action_confirm untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'.""" + if self._name != 'mrp.production': + return super(MrpProduction, self).action_confirm() + + result = super(MrpProduction, self).action_confirm() + + for record in self: + if record.sale_order and record.state == 'confirmed': + message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name) + record.sale_order.message_post(body=message) + + return result \ No newline at end of file -- cgit v1.2.3 From beb653de0340d270f2d56dd7b7145c3552e91ab4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Mar 2025 09:37:33 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 42 ++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index c5b6387d..c3febc02 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -23,9 +23,10 @@ _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1l class StockPicking(models.Model): _inherit = 'stock.picking' + _order = 'final_seq ASC' + konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True) scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True) check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True) - _order = 'final_seq ASC' check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True) @@ -1585,6 +1586,29 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + @api.onchange('koli_id') + def _onchange_koli_compare_with_konfirm_koli(self): + if not self.koli_id: + return + + # Pastikan konfirm_koli_lines tidak kosong + if not self.picking_id.konfirm_koli_lines: + raise UserError(_('Konfirm Koli Harus Diisi!')) + + # Ambil origin picking dari koli yang dipilih + koli_picking = self.koli_id.picking_id._origin + + # Kumpulkan semua origin picking dari konfirm koli lines + konfirm_pick_ids = [ + line.pick_id._origin + for line in self.picking_id.konfirm_koli_lines + if line.pick_id + ] + + # Validasi apakah koli_picking ada dalam daftar konfirmasi + if koli_picking not in konfirm_pick_ids: + raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + @api.constrains('picking_id', 'koli_id') def _check_duplicate_koli(self): for record in self: @@ -1713,6 +1737,22 @@ class ScanKoli(models.Model): # return remaining_scans +class KonfirmKoli(models.Model): + _name = 'konfirm.koli' + _description = 'Konfirm Koli' + _order = 'picking_id, id' + _rec_name = 'pick_id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + pick_id = fields.Many2one('stock.picking', string='Pick') + class WarningModalWizard(models.TransientModel): _name = 'warning.modal.wizard' _description = 'Peringatan Koli Belum Diperiksa' -- cgit v1.2.3 From 5825faf58e86681f62eeeaf783bb2ac0c01afbf7 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 24 Mar 2025 09:52:35 +0700 Subject: cr name request by widya --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index caa420e5..565b0315 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -110,7 +110,7 @@ class UserPengajuanTempoRequest(models.Model): pic_tittle = fields.Char(string='Tittle PIC Penerimaan Barang', related='pengajuan_tempo_id.pic_tittle', store=True, readonly=False) pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Barang', related='pengajuan_tempo_id.pic_mobile', store=True, readonly=False) pic_name = fields.Char(string='Nama PIC Penerimaan Barang', related='pengajuan_tempo_id.pic_name', store=True, readonly=False) - street_pengiriman = fields.Char(string="Alamat Perusahaan", related='pengajuan_tempo_id.street_pengiriman', store=True, readonly=False) + street_pengiriman = fields.Char(string="Alamat Pengiriman Barang", related='pengajuan_tempo_id.street_pengiriman', store=True, readonly=False) state_id_pengiriman = fields.Many2one('res.country.state', string='State', related='pengajuan_tempo_id.state_id_pengiriman', store=True, readonly=False) city_id_pengiriman = fields.Many2one('vit.kota', string='City', related='pengajuan_tempo_id.city_id_pengiriman', store=True, readonly=False) district_id_pengiriman = fields.Many2one('vit.kecamatan', string='Kecamatan',related='pengajuan_tempo_id.district_id_pengiriman', store=True, readonly=False) @@ -119,7 +119,7 @@ class UserPengajuanTempoRequest(models.Model): invoice_pic_tittle = fields.Char(string='Tittle PIC Penerimaan Invoice', related='pengajuan_tempo_id.invoice_pic_tittle', store=True, readonly=False) invoice_pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Invoice', related='pengajuan_tempo_id.invoice_pic_mobile', store=True, readonly=False) invoice_pic = fields.Char(string='Nama PIC Penerimaan Invoice', related='pengajuan_tempo_id.invoice_pic', store=True, readonly=False) - street_invoice = fields.Char(string="Alamat Perusahaan", related='pengajuan_tempo_id.street_invoice', store=True, readonly=False) + street_invoice = fields.Char(string="Alamat Pengiriman Invoice", related='pengajuan_tempo_id.street_invoice', store=True, readonly=False) state_id_invoice = fields.Many2one('res.country.state', string='State', related='pengajuan_tempo_id.state_id_invoice', store=True, readonly=False) city_id_invoice = fields.Many2one('vit.kota', string='City', related='pengajuan_tempo_id.city_id_invoice', store=True, readonly=False) district_id_invoice = fields.Many2one('vit.kecamatan', string='Kecamatan', related='pengajuan_tempo_id.district_id_invoice', store=True, readonly=False) -- cgit v1.2.3 From 99626f917b032110fe12b9a0ee86c218c0367be1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Mar 2025 15:34:56 +0700 Subject: push --- indoteknik_custom/models/mrp_production.py | 169 ++++++++++++++++++++++++++++- indoteknik_custom/models/stock_move.py | 14 +++ 2 files changed, 181 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 54d90256..ed05de91 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -1,4 +1,7 @@ -from odoo import fields, models, api, _ +from odoo import models, fields, api, tools, _ +from datetime import datetime, timedelta +import math +import logging from odoo.exceptions import AccessError, UserError, ValidationError @@ -6,5 +9,167 @@ class MrpProduction(models.Model): _inherit = 'mrp.production' desc = fields.Text(string='Description') + status = fields.Selection([('pending', 'Pending'), ('approved', 'Approved'), ('reject', 'Reject')], string='Status', default='pending', tracking=3) + production_purchase_match = fields.One2many('production.purchase.match', 'production_id', string='Purchase Matches', auto_join=True) + + def action_approve(self): + if self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and self.status == 'pending': + self.status = 'approved' + + def action_reject(self): + if self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and self.status == 'pending': + self.status = 'reject' + + def create_po_from_manufacturing(self): + if not self.status == 'approved': + raise UserError('Harus Di Approve oleh Merchandiser') + + if not self.move_raw_ids: + raise UserError('Tidak ada Lines, belum bisa create PO') + # if self.is_po: + # raise UserError('Sudah pernah di create PO') + + vendor_ids = self.env['stock.move'].read_group([ + ('raw_material_production_id', '=', self.id), + ('vendor_id', '!=', False) + ], fields=['vendor_id'], groupby=['vendor_id']) + + po_ids = [] + for vendor in vendor_ids: + result_po = self.create_po_by_vendor(vendor['vendor_id'][0]) + po_ids += result_po + return { + 'name': _('Purchase Order'), + 'view_mode': 'tree,form', + 'res_model': 'purchase.order', + 'target': 'current', + 'type': 'ir.actions.act_window', + 'domain': [('id', 'in', po_ids)], + } + + + def create_po_by_vendor(self, vendor_id): + current_time = datetime.now() + + PRODUCT_PER_PO = 20 + + stock_move = self.env['stock.move'] + + param_header = { + 'partner_id': vendor_id, + # 'partner_ref': self.sale_order_id.name, + 'currency_id': 12, + 'user_id': self.env.user.id, + 'company_id': 1, # indoteknik dotcom gemilang + 'picking_type_id': 28, # indoteknik bandengan receipts + 'date_order': current_time, + # 'sale_order_id': self.sale_order_id.id, + 'note_description': 'from Manufacturing Order' + } + + domain = [ + ('raw_material_production_id', '=', self.id), + ('vendor_id', '=', vendor_id), + ('state', 'in', ['waiting','confirmed','partially_available']) + ] + + products_len = stock_move.search_count(domain) + page = math.ceil(products_len / PRODUCT_PER_PO) + po_ids = [] + # i start from zero (0) + for i in range(page): + new_po = self.env['purchase.order'].create([param_header]) + new_po.name = new_po.name + "/MO/" + str(i + 1) + po_ids.append(new_po.id) + lines = stock_move.search( + domain, + offset=i * PRODUCT_PER_PO, + limit=PRODUCT_PER_PO + ) + tax = [22] + + for line in lines: + product = line.product_id + price, taxes, vendor = self._get_purchase_price(product) + + param_line = { + 'order_id' : new_po.id, + 'product_id': product.id, + 'product_qty': line.product_uom_qty if line.state in ['confirmed', 'waiting'] else line.product_uom_qty - line.forecast_availability, + 'product_uom_qty': line.product_uom_qty if line.state in ['confirmed', 'waiting'] else line.product_uom_qty - line.forecast_availability, + 'name': product.display_name, + 'price_unit': price, + 'taxes_id': [taxes], + } + new_po_line = self.env['purchase.order.line'].create([param_line]) + + self.env['production.purchase.match'].create([{ + 'production_id': self.id, + 'order_id': new_po.id + }]) + # self.is_po = True + + return po_ids + + def _get_purchase_price(self, product_id): + override_vendor = product_id.x_manufacture.override_vendor_id + if override_vendor: + query = [('product_id', '=', product_id.id), + ('vendor_id', '=', override_vendor.id)] + purchase_price = self.env['purchase.pricelist'].search(query, limit=1) + return self._get_valid_purchase_price(purchase_price) + else: + purchase_price = self.env['purchase.pricelist'].search( + [('product_id', '=', product_id.id), + ('is_winner', '=', True)], + limit=1) + + return self._get_valid_purchase_price(purchase_price) + + def _get_valid_purchase_price(self, purchase_price): + current_time = datetime.now() + delta_time = current_time - timedelta(days=365) + # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') + + price = 0 + taxes = '' + vendor_id = '' + human_last_update = purchase_price.human_last_update or datetime.min + system_last_update = purchase_price.system_last_update or datetime.min + + if purchase_price.taxes_product_id.type_tax_use == 'purchase': + price = purchase_price.product_price + taxes = purchase_price.taxes_product_id.id + vendor_id = purchase_price.vendor_id.id + if delta_time > human_last_update: + price = 0 + taxes = '' + vendor_id = '' + + if system_last_update > human_last_update: + if purchase_price.taxes_system_id.type_tax_use == 'purchase': + price = purchase_price.system_price + taxes = purchase_price.taxes_system_id.id + vendor_id = purchase_price.vendor_id.id + if delta_time > system_last_update: + price = 0 + taxes = '' + vendor_id = '' + + return price, taxes, vendor_id - \ No newline at end of file + +class ProductionPurchaseMatch(models.Model): + _name = 'production.purchase.match' + _order = 'production_id, id' + + production_id = fields.Many2one('mrp.production', string='Ref', required=True, ondelete='cascade', index=True, copy=False) + order_id = fields.Many2one('purchase.order', string='Purchase Order') + vendor = fields.Char(string='Vendor', compute='_compute_info_po') + total = fields.Float(string='Total', compute='_compute_info_po') + + def _compute_info_po(self): + for match in self: + match.vendor = match.order_id.partner_id.name + match.total = match.order_id.amount_total + diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 6b631713..b5fc782e 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -13,6 +13,20 @@ class StockMove(models.Model): ) qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') barcode = fields.Char(string='Barcode', related='product_id.barcode') + vendor_id = fields.Many2one('res.partner' ,string='Vendor') + + @api.onchange('product_id') + def onchange_product_to_fill_vendor(self): + if self.product_id: + if self.product_id.x_manufacture.override_vendor_id: + self.vendor_id = self.product_id.x_manufacture.override_vendor_id.id + else: + purchase_pricelist = self.env['purchase.pricelist'].search( + [('product_id', '=', product_id.id), + ('is_winner', '=', True)], + limit=1) + if purchase_pricelist: + self.vendor_id = purchase_pricelist.vendor_id.id def _compute_qr_code_variant(self): for rec in self: -- cgit v1.2.3 From d094b9634a6d32549655c99ff370e45fb568f11d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Mar 2025 11:06:32 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index c3febc02..0e425f68 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -937,9 +937,12 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): + if len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) + if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) - + if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) @@ -1593,7 +1596,7 @@ class ScanKoli(models.Model): # Pastikan konfirm_koli_lines tidak kosong if not self.picking_id.konfirm_koli_lines: - raise UserError(_('Konfirm Koli Harus Diisi!')) + raise UserError(_('Mapping Koli Harus Diisi!')) # Ambil origin picking dari koli yang dipilih koli_picking = self.koli_id.picking_id._origin @@ -1607,7 +1610,7 @@ class ScanKoli(models.Model): # Validasi apakah koli_picking ada dalam daftar konfirmasi if koli_picking not in konfirm_pick_ids: - raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) @api.constrains('picking_id', 'koli_id') def _check_duplicate_koli(self): -- cgit v1.2.3 From db3ff0677f9c6ddd0f04ebcf3e9c780045259f73 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Mar 2025 13:48:16 +0700 Subject: fix get purchase pricelist --- indoteknik_custom/models/mrp_production.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index ed05de91..0e17fda9 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -98,8 +98,8 @@ class MrpProduction(models.Model): 'product_qty': line.product_uom_qty if line.state in ['confirmed', 'waiting'] else line.product_uom_qty - line.forecast_availability, 'product_uom_qty': line.product_uom_qty if line.state in ['confirmed', 'waiting'] else line.product_uom_qty - line.forecast_availability, 'name': product.display_name, - 'price_unit': price, - 'taxes_id': [taxes], + 'price_unit': price if price else 0.0, + 'taxes_id': [taxes] if taxes else [], } new_po_line = self.env['purchase.order.line'].create([param_line]) @@ -113,10 +113,10 @@ class MrpProduction(models.Model): def _get_purchase_price(self, product_id): override_vendor = product_id.x_manufacture.override_vendor_id - if override_vendor: - query = [('product_id', '=', product_id.id), - ('vendor_id', '=', override_vendor.id)] - purchase_price = self.env['purchase.pricelist'].search(query, limit=1) + query = [('product_id', '=', product_id.id), + ('vendor_id', '=', override_vendor.id)] + purchase_price = self.env['purchase.pricelist'].search(query, limit=1) + if purchase_price: return self._get_valid_purchase_price(purchase_price) else: purchase_price = self.env['purchase.pricelist'].search( -- cgit v1.2.3 From 459790b31fac1a789efcb946f26938b60b123aab Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Mar 2025 15:15:05 +0700 Subject: add field date_approve and position --- indoteknik_custom/models/commision.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index d6b170d2..f94d3c7a 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -156,7 +156,6 @@ class CustomerCommision(models.Model): ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), - ('pengajuan5', 'Menunggu Approval Finance'), ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange',default='draft') @@ -165,7 +164,6 @@ class CustomerCommision(models.Model): ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), - ('pengajuan5', 'Menunggu Approval Finance'), ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status') @@ -194,6 +192,18 @@ class CustomerCommision(models.Model): grouped_so_number = fields.Char(string='Group SO Number', compute='_compute_grouped_numbers') grouped_invoice_number = fields.Char(string='Group Invoice Number', compute='_compute_grouped_numbers') + sales_id = fields.Many2one('res.users', string="Sales", tracking=True) + + date_approved_sales = fields.Datetime(string="Date Approved Sales", tracking=True) + date_approved_marketing = fields.Datetime(string="Date Approved Marketing", tracking=True) + date_approved_pimpinan = fields.Datetime(string="Date Approved Pimpinan", tracking=True) + date_approved_accounting = fields.Datetime(string="Date Approved Accounting", tracking=True) + + position_sales = fields.Char(string="Position Sales", tracking=True) + position_marketing = fields.Char(string="Position Marketing", tracking=True) + position_pimpinan = fields.Char(string="Position Pimpinan", tracking=True) + position_accounting = fields.Char(string="Position Accounting", tracking=True) + def compute_delivery_amt_text(self): tb = Terbilang() @@ -283,25 +293,31 @@ class CustomerCommision(models.Model): return result def action_confirm_customer_commision(self): + now = datetime.utcnow() if not self.status or self.status == 'draft': self.status = 'pengajuan1' elif self.status == 'pengajuan1' and self.env.user.is_sales_manager: self.status = 'pengajuan2' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + self.date_approved_sales = now + self.position_sales = 'Sales Manager' elif self.status == 'pengajuan2' and self.env.user.id == 19: self.status = 'pengajuan3' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + self.date_approved_marketing = now + self.position_marketing = 'Marketing Manager' elif self.status == 'pengajuan3' and self.env.user.is_leader: self.status = 'pengajuan4' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + self.date_approved_pimpinan = now + self.position_pimpinan = 'Pimpinan' elif self.status == 'pengajuan4' and self.env.user.id == 1272: - self.status = 'pengajuan5' - self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name - elif self.status == 'pengajuan5' and self.env.user.id == 23: for line in self.commision_lines: line.invoice_id.is_customer_commision = True self.status = 'approved' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name + self.date_approved_accounting = now + self.position_accounting = 'Accounting' else: raise UserError('Harus di approved oleh yang bersangkutan') return @@ -320,7 +336,6 @@ class CustomerCommision(models.Model): for commision in self: commision.status = commision.last_status if commision.last_status else 'draft' - def action_confirm_customer_payment(self): if self.status != 'approved': raise UserError('Commision harus di approve terlebih dahulu sebelum di konfirmasi pembayarannya') -- cgit v1.2.3 From cc5362aed8b221289416457b72ab11d2aed49b83 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Tue, 25 Mar 2025 15:36:28 +0700 Subject: CR - delivery amt ci vita --- indoteknik_custom/models/sale_order.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b17df045..a1e43b03 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -653,6 +653,14 @@ class SaleOrder(models.Model): if self.email and not re.match(pattern, self.email): raise UserError('Email yang anda input kurang valid') + def _validate_delivery_amt(self): + if self.delivery_amt < 1: + if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik'): + if(self.carrier_id.id == 1): + raise UserError('Untuk Kurir Indoteknik Delivery, Estimasi Ongkos Kirim Harus di isi') + else: + raise UserError('Untuk Shipping Covered Indoteknik, Estimasi Ongkos Kirim Harus di isi') + def override_allow_create_invoice(self): if not self.env.user.is_accounting: raise UserError('Hanya Finance Accounting yang dapat klik tombol ini') @@ -1672,6 +1680,7 @@ class SaleOrder(models.Model): order = super(SaleOrder, self).create(vals) order._compute_etrts_date() order._validate_expected_ready_ship_date() + order._validate_delivery_amt() # order._update_partner_details() return order @@ -1714,6 +1723,7 @@ class SaleOrder(models.Model): "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") res = super(SaleOrder, self).write(vals) + self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() return res \ No newline at end of file -- cgit v1.2.3 From 88fc7a19f0493269d158c72067bc1ef403364f5f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Mar 2025 09:29:51 +0700 Subject: cr so --- indoteknik_custom/models/sale_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index a1e43b03..e7830780 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1680,7 +1680,7 @@ class SaleOrder(models.Model): order = super(SaleOrder, self).create(vals) order._compute_etrts_date() order._validate_expected_ready_ship_date() - order._validate_delivery_amt() + # order._validate_delivery_amt() # order._update_partner_details() return order @@ -1723,7 +1723,7 @@ class SaleOrder(models.Model): "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") res = super(SaleOrder, self).write(vals) - self._validate_delivery_amt() + # self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() return res \ No newline at end of file -- cgit v1.2.3 From 2e3d5ef3c04c694716172f11ae064c07d9ccf942 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Mar 2025 10:30:57 +0700 Subject: cr so --- indoteknik_custom/models/sale_order.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index e7830780..d146fec0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -237,7 +237,8 @@ class SaleOrder(models.Model): @api.constrains('shipping_option_id') def _check_shipping_option(self): for rec in self: - rec.delivery_amt = rec.shipping_option_id.price + if rec.shipping_option_id: + rec.delivery_amt = rec.shipping_option_id.price def _compute_shipping_method_picking(self): for order in self: @@ -1680,7 +1681,7 @@ class SaleOrder(models.Model): order = super(SaleOrder, self).create(vals) order._compute_etrts_date() order._validate_expected_ready_ship_date() - # order._validate_delivery_amt() + order._validate_delivery_amt() # order._update_partner_details() return order @@ -1723,7 +1724,7 @@ class SaleOrder(models.Model): "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") res = super(SaleOrder, self).write(vals) - # self._validate_delivery_amt() + self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() return res \ No newline at end of file -- cgit v1.2.3 From 2b8b899f68fa4ef04a04fdebbd3baa1f5217d0c6 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 26 Mar 2025 10:39:41 +0700 Subject: bug fix total percent margin --- indoteknik_custom/models/sale_order.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index a1e43b03..eda3caa9 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1424,7 +1424,9 @@ class SaleOrder(models.Model): delivery_amt = order.delivery_amt else: delivery_amt = 0 - order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2) + + # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2) + order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-order.fee_third_party)) * 100, 2) # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2) @api.onchange('sales_tax_id') -- cgit v1.2.3 From aada0aa508549c9357cdee1a16cd7a23d6a38b22 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Mar 2025 11:33:57 +0700 Subject: push --- indoteknik_custom/models/commision.py | 1 + indoteknik_custom/models/sale_order.py | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index f94d3c7a..6d832b85 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -440,6 +440,7 @@ class CustomerCommisionLine(models.Model): tax = fields.Float(string='TaxAmt') total = fields.Float(string='Total') total_percent_margin = fields.Float('Total Margin', related='invoice_id.sale_id.total_percent_margin') + total_margin_excl_third_party = fields.Float('Before Margin', related='invoice_id.sale_id.total_margin_excl_third_party') product_id = fields.Many2one('product.product', string='Product') sale_order_id = fields.Many2one('sale.order', string='Sale Order', related='invoice_id.sale_id') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 01aefe7a..d8c1888f 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -71,6 +71,7 @@ class SaleOrder(models.Model): order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True) total_margin = fields.Float('Total Margin', compute='_compute_total_margin', help="Total Margin in Sales Order Header") total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', help="Total % Margin in Sales Order Header") + total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header") approval_status = fields.Selection([ ('pengajuan1', 'Approval Manager'), ('pengajuan2', 'Approval Pimpinan'), @@ -102,6 +103,7 @@ class SaleOrder(models.Model): domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", help="Dipakai untuk alamat tempel", tracking=True) fee_third_party = fields.Float('Fee Pihak Ketiga') + biaya_lain_lain = fields.Float('Biaya Lain Lain') so_status = fields.Selection([ ('terproses', 'Terproses'), ('sebagian', 'Sebagian Diproses'), @@ -232,6 +234,12 @@ class SaleOrder(models.Model): nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") + @api.constrains('delivery_amt', 'biaya_lain_lain', 'fee_third_party') + def _check_margin_excl_third_party(self): + for rec in self: + if rec.fee_third_party == 0: + rec.total_margin_excl_third_party = rec.total_percent_margin + @api.constrains('shipping_option_id') def _check_shipping_option(self): for rec in self: @@ -243,7 +251,7 @@ class SaleOrder(models.Model): carrier_names = order.picking_ids.mapped('carrier_id.name') order.shipping_method_picking = ', '.join(filter(None, carrier_names)) else: - order.shipping_method_picking = False + order.shipping_method_picking = False @api.onchange('payment_status') def _is_continue_transaction(self): @@ -450,7 +458,7 @@ class SaleOrder(models.Model): delivery_amt = order.delivery_amt else: delivery_amt = 0 - order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2) + order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (order.amount_untaxed-delivery_amt-order.fee_third_party-order.biaya_lain_lain)) * 100, 2) def _compute_date_kirim(self): for rec in self: @@ -1399,7 +1407,7 @@ class SaleOrder(models.Model): delivery_amt = order.delivery_amt else: delivery_amt = 0 - order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2) + order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party-order.biaya_lain_lain)) * 100, 2) # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2) @api.onchange('sales_tax_id') -- cgit v1.2.3 From 44220214132f20202da9f225ed46ecfe14057e2c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Mar 2025 13:16:13 +0700 Subject: push --- indoteknik_custom/models/sale_order.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index d8c1888f..f66da470 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -234,8 +234,8 @@ class SaleOrder(models.Model): nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") - @api.constrains('delivery_amt', 'biaya_lain_lain', 'fee_third_party') - def _check_margin_excl_third_party(self): + @api.constrains('write_date') + def _check_total_margin_excl_third_party(self): for rec in self: if rec.fee_third_party == 0: rec.total_margin_excl_third_party = rec.total_percent_margin @@ -251,7 +251,7 @@ class SaleOrder(models.Model): carrier_names = order.picking_ids.mapped('carrier_id.name') order.shipping_method_picking = ', '.join(filter(None, carrier_names)) else: - order.shipping_method_picking = False + order.shipping_method_picking = False @api.onchange('payment_status') def _is_continue_transaction(self): -- cgit v1.2.3 From cefc6ec6de52f2c79c1760cf88db3375f4956a31 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Mar 2025 13:48:25 +0700 Subject: PUSH --- indoteknik_custom/models/sale_order.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index f66da470..3b3aef6a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -234,7 +234,7 @@ class SaleOrder(models.Model): nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") - @api.constrains('write_date') + @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') def _check_total_margin_excl_third_party(self): for rec in self: if rec.fee_third_party == 0: @@ -1662,6 +1662,7 @@ class SaleOrder(models.Model): # Ensure partner details are updated when a sale order is created order = super(SaleOrder, self).create(vals) order._compute_etrts_date() + order._check_total_margin_excl_third_party() # order._update_partner_details() return order @@ -1704,5 +1705,6 @@ class SaleOrder(models.Model): "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") res = super(SaleOrder, self).write(vals) if 'order_line' in vals: + self._check_total_margin_excl_third_party() self._compute_etrts_date() return res \ No newline at end of file -- cgit v1.2.3 From 20cf871492e9de61669c577d31d2219d092796bf Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Mar 2025 15:02:37 +0700 Subject: fix bug on stock picking --- indoteknik_custom/models/sale_order.py | 2 +- indoteknik_custom/models/stock_picking.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c7fcabbb..1778536b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -656,7 +656,7 @@ class SaleOrder(models.Model): def _validate_delivery_amt(self): if self.delivery_amt < 1: - if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik'): + if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): if(self.carrier_id.id == 1): raise UserError('Untuk Kurir Indoteknik Delivery, Estimasi Ongkos Kirim Harus di isi') else: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6c6cbaa1..a1b28385 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -96,6 +96,7 @@ class StockPicking(models.Model): ('not_paid', 'Customer belum bayar'), ('partial', 'Kirim Parsial'), ('indent', 'Indent'), + ('waiting_schedule', 'Menunggu Jadwal Kirim'), ('self_pickup', 'Barang belum di pickup Customer'), ('expedition_closed', 'Eskpedisi belum buka') ], string='Note Logistic', help='jika field ini diisi maka tidak akan dihitung ke lead time') -- cgit v1.2.3 From 321b0c365c722c4a357d7da17cd25561f1d5f78d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Mar 2025 16:42:47 +0700 Subject: cr other subtotal ppn 12% --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 45fdb8df..906985de 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -81,7 +81,7 @@ class AccountMove(models.Model): def compute_other_subtotal(self): for rec in self: - rec.other_subtotal = rec.amount_untaxed * (11 / 12) + rec.other_subtotal = round(rec.amount_untaxed * (11 / 12)) @api.model def generate_attachment(self, record): -- cgit v1.2.3 From bf59882cd2554229cc20dc75d790686f641522c4 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 27 Mar 2025 09:05:51 +0700 Subject: bug fixing percent margin --- indoteknik_custom/models/sale_order.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bee7d6a7..3bf6ba14 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -662,6 +662,7 @@ class SaleOrder(models.Model): if self.email and not re.match(pattern, self.email): raise UserError('Email yang anda input kurang valid') + @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered') def _validate_delivery_amt(self): if self.delivery_amt < 1: if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): -- cgit v1.2.3 From a83022f278c3fbe6a8ed6ae92a2ffee2e4a7c992 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 27 Mar 2025 09:12:49 +0700 Subject: comment total margin excl third party --- indoteknik_custom/models/sale_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 3bf6ba14..4ebc5d34 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1693,7 +1693,7 @@ class SaleOrder(models.Model): order._compute_etrts_date() order._validate_expected_ready_ship_date() order._validate_delivery_amt() - order._check_total_margin_excl_third_party() + # order._check_total_margin_excl_third_party() # order._update_partner_details() return order @@ -1737,7 +1737,7 @@ class SaleOrder(models.Model): res = super(SaleOrder, self).write(vals) self._validate_delivery_amt() - self._check_total_margin_excl_third_party() + # self._check_total_margin_excl_third_party() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() if 'order_line' in vals: -- cgit v1.2.3 From 6a0664215fab4f63928092d98214acb7b68fdff5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 27 Mar 2025 09:21:16 +0700 Subject: fix bug so --- indoteknik_custom/models/sale_order.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bee7d6a7..89ed0327 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -239,8 +239,14 @@ class SaleOrder(models.Model): @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') def _check_total_margin_excl_third_party(self): for rec in self: - if rec.fee_third_party == 0: - rec.total_margin_excl_third_party = rec.total_percent_margin + if rec.fee_third_party == 0 and rec.total_margin_excl_third_party != rec.total_percent_margin: + # Gunakan direct SQL atau flag context untuk menghindari rekursi + self.env.cr.execute(""" + UPDATE sale_order + SET total_margin_excl_third_party = %s + WHERE id = %s + """, (rec.total_percent_margin, rec.id)) + self.invalidate_cache() @api.constrains('shipping_option_id') def _check_shipping_option(self): @@ -1692,7 +1698,7 @@ class SaleOrder(models.Model): order._compute_etrts_date() order._validate_expected_ready_ship_date() order._validate_delivery_amt() - order._check_total_margin_excl_third_party() + # order._check_total_margin_excl_third_party() # order._update_partner_details() return order @@ -1736,7 +1742,7 @@ class SaleOrder(models.Model): res = super(SaleOrder, self).write(vals) self._validate_delivery_amt() - self._check_total_margin_excl_third_party() + # self._check_total_margin_excl_third_party() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() if 'order_line' in vals: -- cgit v1.2.3 From d34d5dfbf8ef449b57bca9031bd5a28a1bf1928d Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 27 Mar 2025 09:26:43 +0700 Subject: bug fix total percent margin edit --- indoteknik_custom/models/sale_order.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4ebc5d34..993eaed1 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1736,10 +1736,9 @@ class SaleOrder(models.Model): "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") res = super(SaleOrder, self).write(vals) - self._validate_delivery_amt() # self._check_total_margin_excl_third_party() + if any(fields in vals for fields in ['delivery_amt', 'carrier_id', 'shipping_cost_covered']): + self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() - if 'order_line' in vals: - self._compute_etrts_date() return res \ No newline at end of file -- cgit v1.2.3 From e926482af5f2b95ff465445215c77161223ee671 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 27 Mar 2025 14:38:57 +0700 Subject: push --- indoteknik_custom/models/mrp_production.py | 11 ++--------- indoteknik_custom/models/purchase_order.py | 17 +++++++++++++++++ indoteknik_custom/models/purchasing_job.py | 10 ++++++++++ indoteknik_custom/models/purchasing_job_state.py | 3 ++- indoteknik_custom/models/stock_move.py | 4 ++-- 5 files changed, 33 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 0e17fda9..561f5b3c 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -9,19 +9,11 @@ class MrpProduction(models.Model): _inherit = 'mrp.production' desc = fields.Text(string='Description') - status = fields.Selection([('pending', 'Pending'), ('approved', 'Approved'), ('reject', 'Reject')], string='Status', default='pending', tracking=3) production_purchase_match = fields.One2many('production.purchase.match', 'production_id', string='Purchase Matches', auto_join=True) - def action_approve(self): - if self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and self.status == 'pending': - self.status = 'approved' - - def action_reject(self): - if self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and self.status == 'pending': - self.status = 'reject' def create_po_from_manufacturing(self): - if not self.status == 'approved': + if not self.state == 'confirmed': raise UserError('Harus Di Approve oleh Merchandiser') if not self.move_raw_ids: @@ -63,6 +55,7 @@ class MrpProduction(models.Model): 'company_id': 1, # indoteknik dotcom gemilang 'picking_type_id': 28, # indoteknik bandengan receipts 'date_order': current_time, + 'product_bom_id': self.product_id.id, # 'sale_order_id': self.sale_order_id.id, 'note_description': 'from Manufacturing Order' } diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index d90c4a8a..b107f389 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -74,6 +74,7 @@ class PurchaseOrder(models.Model): date_done_picking = fields.Datetime(string='Date Done Picking', compute='get_date_done') bills_dp_id = fields.Many2one('account.move', string='Bills DP') bills_pelunasan_id = fields.Many2one('account.move', string='Bills Pelunasan') + product_bom_id = fields.Many2one('product.product', string='Product Bom') grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery', compute='_compute_grand_total') total_margin_match = fields.Float(string='Total Margin Match', compute='_compute_total_margin_match') approve_by = fields.Many2one('res.users', string='Approve By') @@ -726,9 +727,25 @@ class PurchaseOrder(models.Model): self.unlink_purchasing_job_state() self._check_qty_plafon_product() + if self.product_bom_id: + self._remove_product_bom() return res + def _remove_product_bom(self): + pj = self.env['v.purchasing.job'].search([ + ('product_id', '=', self.product_bom_id.id) + ]) + + if pj: + pj_state = self.env['purchasing.job.state'].search([ + ('purchasing_job_id', '=', pj.id) + ]) + + if pj_state: + pj_state.note = 'Product BOM Sudah Di PO' + pj_state.date_po = datetime.utcnow() + def check_ppn_mix(self): reference_taxes = self.order_line[0].taxes_id diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 902bc34b..862e72c7 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -25,6 +25,15 @@ class PurchasingJob(models.Model): ], string='APO?') purchase_representative_id = fields.Many2one('res.users', string="Purchase Representative", readonly=True) note = fields.Char(string="Note Detail") + date_po = fields.Datetime(string='Date PO', copy=False) + + def unlink(self): + # Example: Delete related records from the underlying model + underlying_records = self.env['purchasing.job'].search([ + ('product_id', 'in', self.mapped('product_id').ids) + ]) + underlying_records.unlink() + return super(PurchasingJob, self).unlink() def redirect_to_pjs(self): states = self.env['purchasing.job.state'].search([ @@ -56,6 +65,7 @@ class PurchasingJob(models.Model): pmp.action, max(pjs.status_apo::text) AS status_apo, max(pjs.note::text) AS note, + max(pjs.date_po::text) AS date_po, CASE WHEN pmp.brand IN ('Tekiro', 'RYU', 'Rexco') THEN 27 WHEN sub.vendor_id = 9688 THEN 397 diff --git a/indoteknik_custom/models/purchasing_job_state.py b/indoteknik_custom/models/purchasing_job_state.py index 1838a496..d014edfe 100644 --- a/indoteknik_custom/models/purchasing_job_state.py +++ b/indoteknik_custom/models/purchasing_job_state.py @@ -14,4 +14,5 @@ class PurchasingJobState(models.Model): ('not_apo', 'Belum APO'), ('apo', 'APO') ], string='APO?', copy=False) - note = fields.Char(string="Note Detail") + note = fields.Char(string="Note Detail", copy=False) + date_po = fields.Datetime(string='Date PO', copy=False) diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index b5fc782e..87b1c94e 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -15,8 +15,8 @@ class StockMove(models.Model): barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') - @api.onchange('product_id') - def onchange_product_to_fill_vendor(self): + @api.constrains('product_id') + def constrains_product_to_fill_vendor(self): if self.product_id: if self.product_id.x_manufacture.override_vendor_id: self.vendor_id = self.product_id.x_manufacture.override_vendor_id.id -- cgit v1.2.3 From b105f669873645f29314be55aac45d95d0556970 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 27 Mar 2025 16:20:05 +0700 Subject: fix bug --- indoteknik_custom/models/mrp_production.py | 7 ++++++- indoteknik_custom/models/stock_move.py | 21 +++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 1813dbeb..d80df2ce 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -11,6 +11,7 @@ class MrpProduction(models.Model): desc = fields.Text(string='Description') sale_order = fields.Many2one('sale.order', string='Sale Order', required=True, copy=False) production_purchase_match = fields.One2many('production.purchase.match', 'production_id', string='Purchase Matches', auto_join=True) + is_po = fields.Boolean(string='Is PO') def action_confirm(self): """Override action_confirm untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'.""" @@ -31,6 +32,9 @@ class MrpProduction(models.Model): if not self.state == 'confirmed': raise UserError('Harus Di Approve oleh Merchandiser') + if self.is_po == True: + raise UserError('Sudah pernah di buat PO') + if not self.move_raw_ids: raise UserError('Tidak ada Lines, belum bisa create PO') # if self.is_po: @@ -115,7 +119,8 @@ class MrpProduction(models.Model): 'production_id': self.id, 'order_id': new_po.id }]) - # self.is_po = True + + self.is_po = True return po_ids diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 87b1c94e..514acad0 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -17,16 +17,17 @@ class StockMove(models.Model): @api.constrains('product_id') def constrains_product_to_fill_vendor(self): - if self.product_id: - if self.product_id.x_manufacture.override_vendor_id: - self.vendor_id = self.product_id.x_manufacture.override_vendor_id.id - else: - purchase_pricelist = self.env['purchase.pricelist'].search( - [('product_id', '=', product_id.id), - ('is_winner', '=', True)], - limit=1) - if purchase_pricelist: - self.vendor_id = purchase_pricelist.vendor_id.id + for rec in self: + if rec.product_id and rec.bom_line_id: + if rec.product_id.x_manufacture.override_vendor_id: + rec.vendor_id = rec.product_id.x_manufacture.override_vendor_id.id + else: + purchase_pricelist = self.env['purchase.pricelist'].search( + [('product_id', '=', rec.product_id.id), + ('is_winner', '=', True)], + limit=1) + if purchase_pricelist: + rec.vendor_id = purchase_pricelist.vendor_id.id def _compute_qr_code_variant(self): for rec in self: -- cgit v1.2.3 From b9a81c1a9b495571a5cb30993a31eda7c5ab871f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 7 Apr 2025 10:42:33 +0700 Subject: cr validation delivery amt --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 6dd31d89..8d9af692 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -668,7 +668,7 @@ class SaleOrder(models.Model): if self.email and not re.match(pattern, self.email): raise UserError('Email yang anda input kurang valid') - @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered') + # @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered') def _validate_delivery_amt(self): if self.delivery_amt < 1: if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): -- cgit v1.2.3 From ccd98307c3b48b25bbbb053caa2dba0cce5117d1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 9 Apr 2025 17:07:30 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 36 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 19 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0e425f68..be033b39 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -943,8 +943,8 @@ class StockPicking(models.Model): if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) - if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': - raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) + # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + # raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada koli! Harap periksa kembali.")) @@ -1594,21 +1594,17 @@ class ScanKoli(models.Model): if not self.koli_id: return - # Pastikan konfirm_koli_lines tidak kosong if not self.picking_id.konfirm_koli_lines: raise UserError(_('Mapping Koli Harus Diisi!')) - # Ambil origin picking dari koli yang dipilih koli_picking = self.koli_id.picking_id._origin - # Kumpulkan semua origin picking dari konfirm koli lines konfirm_pick_ids = [ line.pick_id._origin for line in self.picking_id.konfirm_koli_lines if line.pick_id ] - # Validasi apakah koli_picking ada dalam daftar konfirmasi if koli_picking not in konfirm_pick_ids: raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) @@ -1619,20 +1615,18 @@ class ScanKoli(models.Model): existing_koli = self.search([ ('picking_id', '=', record.picking_id.id), ('koli_id', '=', record.koli_id.id), - ('id', '!=', record.id) # Exclude current record + ('id', '!=', record.id) ]) if existing_koli: raise ValidationError(f"⚠️ Koli '{record.koli_id.display_name}' sudah discan untuk picking ini!") def unlink(self): - picking_ids = set(self.mapped('koli_id.picking_id.id')) # Ambil semua picking_id yang terpengaruh + picking_ids = set(self.mapped('koli_id.picking_id.id')) for scan in self: koli = scan.koli_id.koli_id if koli: - # Hapus reserved_id saat scan dihapus koli.reserved_id = False - # Periksa ulang apakah masih ada scan.koli yang tersisa untuk setiap picking_id for picking_id in picking_ids: remaining_scans = self.env['sales.order.koli'].search_count([ ('koli_id.picking_id', '=', picking_id) @@ -1640,8 +1634,6 @@ class ScanKoli(models.Model): delete_koli = len(self.filtered(lambda rec: rec.koli_id.picking_id.id == picking_id)) - - # Jika tidak ada scan.koli lain yang tersisa, set linked_out_picking_id ke False if remaining_scans == delete_koli: picking = self.env['stock.picking'].browse(picking_id) picking.linked_out_picking_id = False @@ -1666,7 +1658,6 @@ class ScanKoli(models.Model): scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin def _compute_scan_koli_progress(self): - """ Menghitung progres scan koli dalam format 'X/Y' """ for scan in self: if scan.picking_id: all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') @@ -1676,7 +1667,6 @@ class ScanKoli(models.Model): @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): - """ Validasi jika jumlah scan koli melebihi total SO koli """ for scan in self.picking_id.scan_koli_lines: scan.koli_id.koli_id.reserved_id = scan.picking_id.id scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id @@ -1709,24 +1699,21 @@ class ScanKoli(models.Model): koli_count_by_picking = defaultdict(int) for scan in self: - koli_count_by_picking[scan.koli_id.picking_id.id] += 1 # Hitung jumlah koli per picking + koli_count_by_picking[scan.koli_id.picking_id.id] += 1 for picking_id, total_koli in koli_count_by_picking.items(): picking = self.env['stock.picking'].browse(picking_id) if total_koli == picking.quantity_koli: - # Ambil stock moves dari BU/PICK dan BU/OUT berdasarkan picking_id pick_moves = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) out_moves = self.env['stock.move.line'].search([('picking_id', '=', picking.linked_out_picking_id.id)]) - # Sesuaikan product_id di BU/OUT dengan BU/PICK for pick_move in pick_moves: corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) if corresponding_out_move: corresponding_out_move.qty_done += pick_move.qty_done def _reset_qty_done_if_no_scan(self, picking_id): - """Set qty_done ke 0 hanya jika tidak ada scan.koli tersisa untuk picking_id tersebut.""" product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) for move in product_bu_pick: @@ -1756,6 +1743,18 @@ class KonfirmKoli(models.Model): ) pick_id = fields.Many2one('stock.picking', string='Pick') + @api.constrains('pick_id') + def _check_duplicate_pick_id(self): + for rec in self: + exist = self.search([ + ('pick_id', '=', rec.pick_id.id), + ('picking_id', '=', rec.picking_id.id), + ('id', '!=', rec.id), + ]) + + if exist: + raise UserError(f"⚠️ '{rec.pick_id.display_name}' sudah discan untuk picking ini!") + class WarningModalWizard(models.TransientModel): _name = 'warning.modal.wizard' _description = 'Peringatan Koli Belum Diperiksa' @@ -1765,7 +1764,6 @@ class WarningModalWizard(models.TransientModel): picking_id = fields.Many2one('stock.picking') def action_continue(self): - """Lanjutkan validasi setelah menutup wizard""" if self.picking_id: return self.picking_id.with_context(skip_koli_check=True).button_validate() return {'type': 'ir.actions.act_window_close'} -- cgit v1.2.3 From e1edb44855ed2549f6f5a35773c5fdb81de9c0b4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 09:07:24 +0700 Subject: comment function check_product_bom on action confirm so --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8d9af692..8d156943 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1257,7 +1257,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: - order.check_product_bom() + # order.check_product_bom() order.check_credit_limit() order.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: -- cgit v1.2.3 From 4cfda3f1511ba1e6f8226652cf1ff64a48efef92 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 09:18:51 +0700 Subject: uncomment function check_product_bom on so --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8d156943..8d9af692 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1257,7 +1257,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: - # order.check_product_bom() + order.check_product_bom() order.check_credit_limit() order.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: -- cgit v1.2.3 From 00c69ce93bdb0071cd563be855857d2137115868 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 13:36:30 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index be033b39..3aa18233 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -195,6 +195,12 @@ class StockPicking(models.Model): # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) final_seq = fields.Float(string='Remaining Time') + @api.constrains('scan_koli_lines') + def _constrains_scan_koli_lines(self): + now = datetime.datetime.utcnow() + for picking in self: + if len(picking.scan_koli_lines) > 0: + picking.driver_departure_date = now @api.depends('total_so_koli') def _compute_total_so_koli(self): @@ -1568,6 +1574,19 @@ class CheckKoli(models.Model): ) koli = fields.Char(string='Koli') reserved_id = fields.Many2one('stock.picking', string='Reserved Picking') + check_koli_progress = fields.Char( + string="Progress Check Koli" + ) + + @api.constrains('koli') + def _check_koli_progress(self): + for check in self: + if check.picking_id: + all_checks = self.env['check.koli'].search([('picking_id', '=', check.picking_id.id)], order='id') + if all_checks: + check_index = list(all_checks).index(check) + 1 # Nomor urut check + total_so_koli = len(all_checks) + check.check_koli_progress = f"{check_index}/{total_so_koli}" if total_so_koli else "0/0" class ScanKoli(models.Model): _name = 'scan.koli' @@ -1589,6 +1608,15 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + def _compute_scan_koli_progress(self): + for scan in self: + if scan.picking_id: + all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') + if all_scans: + scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan + total_so_koli = scan.picking_id.total_so_koli + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" + @api.onchange('koli_id') def _onchange_koli_compare_with_konfirm_koli(self): if not self.koli_id: @@ -1661,9 +1689,10 @@ class ScanKoli(models.Model): for scan in self: if scan.picking_id: all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') - scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan - total_so_koli = scan.picking_id.total_so_koli - scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" + if all_scans: + scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan + total_so_koli = scan.picking_id.total_so_koli + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): -- cgit v1.2.3 From b3ae9b237bb4ec2861ab6e1e6fc2fad85358fe77 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 13:37:20 +0700 Subject: add brand ryu spareparts on purchasing job --- indoteknik_custom/models/purchasing_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 862e72c7..ea2f46cb 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -67,7 +67,7 @@ class PurchasingJob(models.Model): max(pjs.note::text) AS note, max(pjs.date_po::text) AS date_po, CASE - WHEN pmp.brand IN ('Tekiro', 'RYU', 'Rexco') THEN 27 + WHEN pmp.brand IN ('Tekiro', 'RYU', 'Rexco', 'RYU (Sparepart)') THEN 27 WHEN sub.vendor_id = 9688 THEN 397 WHEN sub.vendor_id = 35475 THEN 397 WHEN sub.vendor_id = 29712 THEN 397 -- cgit v1.2.3 From 4147989e776d82a0a8b06a0ff8901e2146b0bd57 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 13:52:14 +0700 Subject: comment validasi validate bu out --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b1243e95..18edd497 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -995,11 +995,11 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - if len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': - raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) + # if len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + # raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) - if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': - raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + # if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + # raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': # raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) -- cgit v1.2.3 From a24177e4f4f575ea95ebc1d886b830da5c320690 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 14:01:52 +0700 Subject: fix case old so and new so wms validation --- indoteknik_custom/models/stock_picking.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 18edd497..1987c03c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -995,11 +995,19 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - # if len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': - # raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) - - # if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': - # raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + threshold_datetime = datetime(2025, 4, 11, 13, 26) + + if (len(self.konfirm_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and (self.create_date or datetime.now()) > threshold_datetime): + raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) + + if (len(self.scan_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and (self.create_date or datetime.now()) > threshold_datetime): + raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': # raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) -- cgit v1.2.3 From 52b493aaee7c1782c328d2f3af7bee6534342734 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 14:06:29 +0700 Subject: fix error --- indoteknik_custom/models/stock_picking.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1987c03c..932e394b 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -995,18 +995,18 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - threshold_datetime = datetime(2025, 4, 11, 13, 26) + threshold_datetime = waktu(2025, 4, 11, 13, 26) if (len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and (self.create_date or datetime.now()) > threshold_datetime): + and (self.create_date or waktu.utcnow()) > threshold_datetime): raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) if (len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and (self.create_date or datetime.now()) > threshold_datetime): + and (self.create_date or waktu.utcnow()) > threshold_datetime): raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': -- cgit v1.2.3 From 9ca20d51a0aad50ea3df9bd878735c2fb8aadcc3 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 15:32:39 +0700 Subject: push wms --- indoteknik_custom/models/stock_picking.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 932e394b..19b7517c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -148,8 +148,16 @@ class StockPicking(models.Model): # for record in self: # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" quantity_koli = fields.Float(string="Quantity Koli", copy=False) + total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli') - + @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli') + def _compute_total_mapping_koli(self): + for record in self: + total = 0.0 + for line in record.konfirm_koli_lines: + if line.pick_id and line.pick_id.quantity_koli: + total += line.pick_id.quantity_koli + record.total_mapping_koli = total @api.model def _compute_dokumen_tanda_terima(self): @@ -995,18 +1003,18 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - threshold_datetime = waktu(2025, 4, 11, 13, 26) + threshold_datetime = waktu(2025, 4, 11, 6, 26) if (len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and (self.create_date or waktu.utcnow()) > threshold_datetime): + and self.create_date > threshold_datetime): raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) if (len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and (self.create_date or waktu.utcnow()) > threshold_datetime): + and self.create_date > threshold_datetime): raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': @@ -1015,9 +1023,12 @@ class StockPicking(models.Model): if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada koli! Harap periksa kembali.")) + if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name: + raise UserError(_("Tidak ada Check Product! Harap periksa kembali.")) + if self.total_koli > self.total_so_koli: raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") - % (self.total_koli, self.total_so_koli)) + % (self.total_koli, self.t1otal_so_koli)) if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: @@ -1847,7 +1858,7 @@ class KonfirmKoli(models.Model): if exist: raise UserError(f"⚠️ '{rec.pick_id.display_name}' sudah discan untuk picking ini!") - + class WarningModalWizard(models.TransientModel): _name = 'warning.modal.wizard' _description = 'Peringatan Koli Belum Diperiksa' -- cgit v1.2.3 From 1def3707b2392fa17fb71cc70051bbe76cda47aa Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 17:03:27 +0700 Subject: change request state reserve stock picking --- indoteknik_custom/models/stock_picking.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 19b7517c..fd9daec9 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -649,13 +649,13 @@ class StockPicking(models.Model): def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'outgoing'), - ('name', 'ilike', 'BU/OUT/'), + ('picking_type_code', '=', 'internal'), + ('name', 'ilike', 'BU/PICK/'), ]) count = self.search_count([ ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'outgoing') + ('picking_type_code', '=', 'internal') ]) for picking in pickings: @@ -675,8 +675,8 @@ class StockPicking(models.Model): def check_state_reserve_backorder(self): pickings = self.search([ ('backorder_id', '!=', False), - ('name', 'ilike', 'BU/OUT/'), - ('picking_type_code', '=', 'outgoing'), + ('name', 'ilike', 'BU/PICK/'), + ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) -- cgit v1.2.3 From 487772c771e72a77ae4d9e9c865d94d015f1ea5b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 10:44:12 +0700 Subject: refactor code state_reserve --- indoteknik_custom/models/stock_picking.py | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index fd9daec9..558e13e6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -215,6 +215,7 @@ class StockPicking(models.Model): # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) final_seq = fields.Float(string='Remaining Time') + shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): @@ -626,37 +627,15 @@ class StockPicking(models.Model): res = super(StockPicking, self).do_unreserve() current_time = datetime.datetime.utcnow() self.date_unreserve = current_time - # self.check_state_reserve() return res - # def check_state_reserve(self): - # do = self.search([ - # ('state', 'not in', ['cancel', 'draft', 'done']), - # ('picking_type_code', '=', 'outgoing') - # ]) - - # for rec in do: - # rec.state_reserve = 'ready' - # rec.date_reserved = datetime.datetime.utcnow() - - # for line in rec.move_ids_without_package: - # if line.product_uom_qty > line.reserved_availability: - # rec.state_reserve = 'waiting' - # rec.date_reserved = '' - # break - def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'internal'), ('name', 'ilike', 'BU/PICK/'), ]) - - count = self.search_count([ - ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'internal') - ]) for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ @@ -679,12 +658,6 @@ class StockPicking(models.Model): ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) - - count = self.search_count([ - ('backorder_id', '!=', False), - ('picking_type_code', '=', 'outgoing'), - ('state', 'not in', ['cancel', 'draft', 'done']) - ]) for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ -- cgit v1.2.3 From 17b8688b83b65b0c21034ffcc9e51baf1099618b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 11:19:41 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 558e13e6..2aca5003 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -978,17 +978,17 @@ class StockPicking(models.Model): threshold_datetime = waktu(2025, 4, 11, 6, 26) - if (len(self.konfirm_koli_lines) == 0 - and 'BU/OUT/' in self.name - and self.picking_type_code == 'outgoing' - and self.create_date > threshold_datetime): - raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) - - if (len(self.scan_koli_lines) == 0 - and 'BU/OUT/' in self.name - and self.picking_type_code == 'outgoing' - and self.create_date > threshold_datetime): - raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + # if (len(self.konfirm_koli_lines) == 0 + # and 'BU/OUT/' in self.name + # and self.picking_type_code == 'outgoing' + # and self.create_date > threshold_datetime): + # raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) + + # if (len(self.scan_koli_lines) == 0 + # and 'BU/OUT/' in self.name + # and self.picking_type_code == 'outgoing' + # and self.create_date > threshold_datetime): + # raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': # raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) -- cgit v1.2.3 From 14fb9b00d18a7a0a3746106a1303f4ff1c13c356 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 11:21:06 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 2aca5003..558e13e6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -978,17 +978,17 @@ class StockPicking(models.Model): threshold_datetime = waktu(2025, 4, 11, 6, 26) - # if (len(self.konfirm_koli_lines) == 0 - # and 'BU/OUT/' in self.name - # and self.picking_type_code == 'outgoing' - # and self.create_date > threshold_datetime): - # raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) - - # if (len(self.scan_koli_lines) == 0 - # and 'BU/OUT/' in self.name - # and self.picking_type_code == 'outgoing' - # and self.create_date > threshold_datetime): - # raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + if (len(self.konfirm_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and self.create_date > threshold_datetime): + raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) + + if (len(self.scan_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and self.create_date > threshold_datetime): + raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': # raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) -- cgit v1.2.3 From b73d16bf8dc0546190c9853f3e32a9aeaae3c1f0 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 13:17:02 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 558e13e6..4e926e60 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1056,6 +1056,7 @@ class StockPicking(models.Model): res = super(StockPicking, self).button_validate() self.calculate_line_no() self.date_done = datetime.datetime.utcnow() + self.driver_departure_date = datetime.datetime.utcnow() self.state_reserve = 'done' self.final_seq = 0 self.send_koli_to_so() -- cgit v1.2.3 From a4d19c6b9f026cc247c135b14a6fecf76a9fcd70 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 15:17:17 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 55 ++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4e926e60..76ba51d4 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -76,6 +76,11 @@ class StockPicking(models.Model): readonly=True, copy=False ) + out_code = fields.Integer( + string="Out Code", + readonly=True, + related="id", + ) sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan", ) paket_documentation = fields.Binary(string="Dokumentasi Paket", ) sj_return_date = fields.Datetime(string="SJ Return Date", ) @@ -216,6 +221,16 @@ class StockPicking(models.Model): # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) final_seq = fields.Float(string='Remaining Time') shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') + state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') + + @api.constrains('konfirm_koli_lines') + def _constrains_konfirm_koli_lines(self): + now = datetime.datetime.utcnow() + for picking in self: + if len(picking.konfirm_koli_lines) > 0: + picking.state_packing = 'packing_done' + else: + picking.state_packing = 'not_packing' @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): @@ -1059,6 +1074,7 @@ class StockPicking(models.Model): self.driver_departure_date = datetime.datetime.utcnow() self.state_reserve = 'done' self.final_seq = 0 + self.set_picking_code_out() self.send_koli_to_so() if not self.env.context.get('skip_koli_check'): for picking in self: @@ -1088,6 +1104,26 @@ class StockPicking(models.Model): self.send_mail_bills() return res + def set_picking_code_out(self): + for picking in self: + # Check if picking meets criteria + is_bu_pick = picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name + if not is_bu_pick: + continue + + # Find matching outgoing transfers + bu_out_transfers = self.search([ + ('name', 'like', 'BU/OUT/%'), + ('sale_id', '=', picking.sale_id.id), + ('picking_type_code', '=', 'outgoing'), + ('picking_code', '=', False), + ('state', 'not in', ['done', 'cancel']) + ]) + + # Assign sequence code to each matching transfer + for transfer in bu_out_transfers: + transfer.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code') + def check_koli(self): for picking in self: @@ -1206,11 +1242,28 @@ class StockPicking(models.Model): def create(self, vals): self._use_faktur(vals) records = super(StockPicking, self).create(vals) - + + # Panggil sync_sale_line setelah record dibuat + # records.sync_sale_line(vals) return records + def sync_sale_line(self, vals): + # Pastikan kita bekerja dengan record yang sudah ada + for picking in self: + if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name: + for line in picking.move_ids_without_package: + if line.product_id and picking.sale_id: + sale_line = self.env['sale.order.line'].search([ + ('product_id', '=', line.product_id.id), + ('order_id', '=', picking.sale_id.id) + ], limit=1) # Tambahkan limit=1 untuk efisiensi + + if sale_line: + line.sale_line_id = sale_line.id + def write(self, vals): self._use_faktur(vals) + self.sync_sale_line(vals) for picking in self: # Periksa apakah kondisi terpenuhi saat data diubah if (vals.get('picking_type_code', picking.picking_type_code) == 'incoming' and -- cgit v1.2.3 From 3e22bea62b4c57268ce777d34ec6d19aede8b0c1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 16:13:00 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 76ba51d4..8755a1f3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -154,6 +154,7 @@ class StockPicking(models.Model): # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" quantity_koli = fields.Float(string="Quantity Koli", copy=False) total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli') + so_lama = fields.Boolean('SO LAMA') @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli') def _compute_total_mapping_koli(self): @@ -996,13 +997,15 @@ class StockPicking(models.Model): if (len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and self.create_date > threshold_datetime): + and self.create_date > threshold_datetime + and not self.so_lama): raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) if (len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and self.create_date > threshold_datetime): + and self.create_date > threshold_datetime + and not self.so_lama): raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': -- cgit v1.2.3 From 3176f8497009169294e25f7f461f1a81c6bd0c59 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 15 Apr 2025 11:23:52 +0700 Subject: push --- indoteknik_custom/models/delivery_order.py | 4 +-- indoteknik_custom/models/stock_picking.py | 47 +++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/delivery_order.py b/indoteknik_custom/models/delivery_order.py index 3473197b..2fc574c4 100644 --- a/indoteknik_custom/models/delivery_order.py +++ b/indoteknik_custom/models/delivery_order.py @@ -24,7 +24,7 @@ class DeliveryOrder(models.TransientModel): for delivery_order_line in vals['delivery_order_line_ids']: picking = False if delivery_order_line[2]['name']: - picking = self.env['stock.picking'].search([('picking_code', '=', delivery_order_line[2]['name'])], limit=1) + picking = self.env['stock.picking'].search([('out_code', '=', delivery_order_line[2]['name'])], limit=1) if picking: line_tracking_no = delivery_order_line[2]['tracking_no'] @@ -85,7 +85,7 @@ class DeliveryOrderLine(models.TransientModel): if self.name: if len(self.name) == 13: self.name = self.name[:-1] - picking = self.env['stock.picking'].search([('picking_code', '=', self.name)], limit=1) + picking = self.env['stock.picking'].search([('out_code', '=', self.name)], limit=1) if picking: if picking.driver_id: self.driver_id = picking.driver_id diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 8755a1f3..ae17b5d1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -155,7 +155,32 @@ class StockPicking(models.Model): quantity_koli = fields.Float(string="Quantity Koli", copy=False) total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli') so_lama = fields.Boolean('SO LAMA') - + linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out') + + # def write(self, vals): + # if 'linked_manual_bu_out' in vals: + # for record in self: + # if (record.picking_type_code == 'internal' + # and 'BU/PICK/' in record.name): + # # Jika menghapus referensi (nilai di-set False/None) + # if record.linked_manual_bu_out and not vals['linked_manual_bu_out']: + # record.linked_manual_bu_out.state_packing = 'not_packing' + # # Jika menambahkan referensi baru + # elif vals['linked_manual_bu_out']: + # new_picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) + # new_picking.state_packing = 'packing_done' + # return super().write(vals) + + # @api.model + # def create(self, vals): + # record = super().create(vals) + # if (record.picking_type_code == 'internal' + # and 'BU/PICK/' in record.name + # and vals.get('linked_manual_bu_out')): + # picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) + # picking.state_packing = 'packing_done' + # return record + @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli') def _compute_total_mapping_koli(self): for record in self: @@ -224,15 +249,6 @@ class StockPicking(models.Model): shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') - @api.constrains('konfirm_koli_lines') - def _constrains_konfirm_koli_lines(self): - now = datetime.datetime.utcnow() - for picking in self: - if len(picking.konfirm_koli_lines) > 0: - picking.state_packing = 'packing_done' - else: - picking.state_packing = 'not_packing' - @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): now = datetime.datetime.utcnow() @@ -1265,6 +1281,17 @@ class StockPicking(models.Model): line.sale_line_id = sale_line.id def write(self, vals): + if 'linked_manual_bu_out' in vals: + for record in self: + if (record.picking_type_code == 'internal' + and 'BU/PICK/' in record.name): + # Jika menghapus referensi (nilai di-set False/None) + if record.linked_manual_bu_out and not vals['linked_manual_bu_out']: + record.linked_manual_bu_out.state_packing = 'not_packing' + # Jika menambahkan referensi baru + elif vals['linked_manual_bu_out']: + new_picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) + new_picking.state_packing = 'packing_done' self._use_faktur(vals) self.sync_sale_line(vals) for picking in self: -- cgit v1.2.3 From d04df4f0f78e34dbaf1ce7f8ea1102ac2bdf019c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 15 Apr 2025 11:41:14 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ae17b5d1..383c75a3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1030,6 +1030,9 @@ class StockPicking(models.Model): if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada koli! Harap periksa kembali.")) + if not self.linked_manual_bu_out and 'BU/PICK/' in self.name: + raise UserError(_("Isi BU Out terlebih dahulu!")) + if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada Check Product! Harap periksa kembali.")) @@ -1090,7 +1093,6 @@ class StockPicking(models.Model): res = super(StockPicking, self).button_validate() self.calculate_line_no() self.date_done = datetime.datetime.utcnow() - self.driver_departure_date = datetime.datetime.utcnow() self.state_reserve = 'done' self.final_seq = 0 self.set_picking_code_out() -- cgit v1.2.3 From e9f38f540f92d6ef98b0153cbf17ce064932ad60 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Tue, 15 Apr 2025 16:47:04 +0700 Subject: fix: add minimun delivery_amt + alert --- indoteknik_custom/models/sale_order.py | 80 ++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c83ffd61..36feb70b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -315,8 +315,12 @@ class SaleOrder(models.Model): "sale_order_id": self.id, }) self.shipping_option_id = shipping_option.id - + def action_estimate_shipping(self): + # Pengecekan Minimum Delivery Amount + if self.delivery_amt < 5000: + raise UserError("Estimasi ongkos kirim belum mencapai jumlah minimum untuk pengiriman") + if self.carrier_id.id in [1, 151]: self.action_indoteknik_estimate_shipping() return @@ -366,10 +370,65 @@ class SaleOrder(models.Model): self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id - self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
Detail Lain:
{'
'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}") + # Jika perlu, tambahkan log atau tindakan lebih lanjut setelah peringatan else: raise UserError("Gagal mendapatkan estimasi ongkir.") + + # def action_estimate_shipping(self): + # if self.carrier_id.id in [1, 151]: + # self.action_indoteknik_estimate_shipping() + # return + + # total_weight = 0 + # missing_weight_products = [] + + # for line in self.order_line: + # if line.weight > 0: + # total_weight += line.weight * line.product_uom_qty + # line.product_id.weight = line.weight + # else: + # missing_weight_products.append(line.product_id.name) + + # if missing_weight_products: + # product_names = '
'.join(missing_weight_products) + # self.message_post(body=f"Produk berikut tidak memiliki berat:
{product_names}") + + # if total_weight == 0: + # raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") + + # destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id + # if not destination_subsdistrict_id: + # raise UserError("Gagal mendapatkan ID kota tujuan.") + + # result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id) + # if result: + # shipping_options = [] + # for courier in result['rajaongkir']['results']: + # for cost_detail in courier['costs']: + # service = cost_detail['service'] + # description = cost_detail['description'] + # etd = cost_detail['cost'][0]['etd'] + # value = cost_detail['cost'][0]['value'] + # shipping_options.append((service, description, etd, value, courier['code'])) + + # self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() + + # for service, description, etd, value, provider in shipping_options: + # self.env["shipping.option"].create({ + # "name": service, + # "price": value, + # "provider": provider, + # "etd": etd, + # "sale_order_id": self.id, + # }) + + # self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id + + # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
Detail Lain:
{'
'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}") + # else: + # raise UserError("Gagal mendapatkan estimasi ongkir.") + def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' headers = { @@ -670,13 +729,26 @@ class SaleOrder(models.Model): raise UserError('Email yang anda input kurang valid') # @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered') + def _validate_delivery_amt(self): + if self.delivery_amt < 5000: + raise UserError("Estimasi Ongkos Kirim belum memenuhi jumlah minimum untuk pengiriman.") + if self.delivery_amt < 1: - if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): - if(self.carrier_id.id == 1): + if (self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): + if self.carrier_id.id == 1: raise UserError('Untuk Kurir Indoteknik Delivery, Estimasi Ongkos Kirim Harus di isi') else: raise UserError('Untuk Shipping Covered Indoteknik, Estimasi Ongkos Kirim Harus di isi') + + + # def _validate_delivery_amt(self): + # if self.delivery_amt < 1: + # if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): + # if(self.carrier_id.id == 1): + # raise UserError('Untuk Kurir Indoteknik Delivery, Estimasi Ongkos Kirim Harus di isi') + # else: + # raise UserError('Untuk Shipping Covered Indoteknik, Estimasi Ongkos Kirim Harus di isi') def override_allow_create_invoice(self): if not self.env.user.is_accounting: -- cgit v1.2.3 From ceba13a481818a7832f4c89035248abf21b94940 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 16 Apr 2025 09:27:05 +0700 Subject: push --- indoteknik_custom/models/delivery_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/delivery_order.py b/indoteknik_custom/models/delivery_order.py index 2fc574c4..3473197b 100644 --- a/indoteknik_custom/models/delivery_order.py +++ b/indoteknik_custom/models/delivery_order.py @@ -24,7 +24,7 @@ class DeliveryOrder(models.TransientModel): for delivery_order_line in vals['delivery_order_line_ids']: picking = False if delivery_order_line[2]['name']: - picking = self.env['stock.picking'].search([('out_code', '=', delivery_order_line[2]['name'])], limit=1) + picking = self.env['stock.picking'].search([('picking_code', '=', delivery_order_line[2]['name'])], limit=1) if picking: line_tracking_no = delivery_order_line[2]['tracking_no'] @@ -85,7 +85,7 @@ class DeliveryOrderLine(models.TransientModel): if self.name: if len(self.name) == 13: self.name = self.name[:-1] - picking = self.env['stock.picking'].search([('out_code', '=', self.name)], limit=1) + picking = self.env['stock.picking'].search([('picking_code', '=', self.name)], limit=1) if picking: if picking.driver_id: self.driver_id = picking.driver_id -- cgit v1.2.3 From f1968b89ca7a32a69150fc6f9a4f848582d13336 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 16 Apr 2025 09:55:13 +0700 Subject: push --- indoteknik_custom/models/delivery_order.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/delivery_order.py b/indoteknik_custom/models/delivery_order.py index 3473197b..aa9c42c2 100644 --- a/indoteknik_custom/models/delivery_order.py +++ b/indoteknik_custom/models/delivery_order.py @@ -25,7 +25,8 @@ class DeliveryOrder(models.TransientModel): picking = False if delivery_order_line[2]['name']: picking = self.env['stock.picking'].search([('picking_code', '=', delivery_order_line[2]['name'])], limit=1) - + if picking: + picking = self.env['stock.picking'].search([('out_code', '=', delivery_order_line[2]['name'])], limit=1) if picking: line_tracking_no = delivery_order_line[2]['tracking_no'] @@ -86,6 +87,10 @@ class DeliveryOrderLine(models.TransientModel): if len(self.name) == 13: self.name = self.name[:-1] picking = self.env['stock.picking'].search([('picking_code', '=', self.name)], limit=1) + + if not picking: + picking = self.env['stock.picking'].search([('out_code', '=', self.name)], limit=1) + if picking: if picking.driver_id: self.driver_id = picking.driver_id -- cgit v1.2.3 From 041bcaaf06a6824493b02cf9dfc1b9d4fa09c6c9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 16 Apr 2025 10:14:54 +0700 Subject: fix bug --- indoteknik_custom/models/delivery_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/delivery_order.py b/indoteknik_custom/models/delivery_order.py index aa9c42c2..2dd0c802 100644 --- a/indoteknik_custom/models/delivery_order.py +++ b/indoteknik_custom/models/delivery_order.py @@ -25,7 +25,7 @@ class DeliveryOrder(models.TransientModel): picking = False if delivery_order_line[2]['name']: picking = self.env['stock.picking'].search([('picking_code', '=', delivery_order_line[2]['name'])], limit=1) - if picking: + if not picking: picking = self.env['stock.picking'].search([('out_code', '=', delivery_order_line[2]['name'])], limit=1) if picking: line_tracking_no = delivery_order_line[2]['tracking_no'] -- cgit v1.2.3 From 2dc56ffaf7e2e6d703eac32fd1213cf84b684915 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 16 Apr 2025 10:25:39 +0700 Subject: (andri)add log note after estimate shipping success --- indoteknik_custom/models/sale_order.py | 129 +++++++++++++-------------------- 1 file changed, 51 insertions(+), 78 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 36feb70b..cc7b9851 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -315,12 +315,22 @@ class SaleOrder(models.Model): "sale_order_id": self.id, }) self.shipping_option_id = shipping_option.id + self.message_post( + body=( + f"Estimasi pengiriman Indoteknik berhasil:
" + f"Layanan: {shipping_option.name}
" + f"ETD: {shipping_option.etd}
" + f"Biaya: Rp {shipping_option.price:,}
" + f"Provider: {shipping_option.provider}" + ), + message_type="comment", + ) def action_estimate_shipping(self): # Pengecekan Minimum Delivery Amount if self.delivery_amt < 5000: raise UserError("Estimasi ongkos kirim belum mencapai jumlah minimum untuk pengiriman") - + if self.carrier_id.id in [1, 151]: self.action_indoteknik_estimate_shipping() return @@ -347,87 +357,50 @@ class SaleOrder(models.Model): raise UserError("Gagal mendapatkan ID kota tujuan.") result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id) - if result: - shipping_options = [] - for courier in result['rajaongkir']['results']: - for cost_detail in courier['costs']: - service = cost_detail['service'] - description = cost_detail['description'] - etd = cost_detail['cost'][0]['etd'] - value = cost_detail['cost'][0]['value'] - shipping_options.append((service, description, etd, value, courier['code'])) - - self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() - - for service, description, etd, value, provider in shipping_options: - self.env["shipping.option"].create({ - "name": service, - "price": value, - "provider": provider, - "etd": etd, - "sale_order_id": self.id, - }) - - self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id - # Jika perlu, tambahkan log atau tindakan lebih lanjut setelah peringatan + if not result: + raise UserError("Estimasi Ongkir gagal sebab pilihan kurir tidak terdaftar di RajaOngkir") + + shipping_options = [] + for courier in result['rajaongkir']['results']: + for cost_detail in courier['costs']: + service = cost_detail['service'] + description = cost_detail['description'] + etd = cost_detail['cost'][0]['etd'] + value = cost_detail['cost'][0]['value'] + shipping_options.append((service, description, etd, value, courier['code'])) + + # Hapus opsi shipping lama + self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() + + # Simpan opsi shipping baru + for service, description, etd, value, provider in shipping_options: + self.env["shipping.option"].create({ + "name": service, + "price": value, + "provider": provider, + "etd": etd, + "sale_order_id": self.id, + }) + + # Set opsi shipping default + first_option = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1) + if first_option: + self.shipping_option_id = first_option.id + message = ( + f"Estimasi pengiriman berhasil:
" + f"Layanan: {first_option.name}
" + f"Deskripsi: {dict(first_option._fields['name'].selection).get(first_option.name, first_option.name)}
" + f"ETD: {first_option.etd} hari
" + f"Biaya: Rp {first_option.price:,}" + ) + self.message_post( + body=message, + message_type="comment", + ) else: raise UserError("Gagal mendapatkan estimasi ongkir.") - - # def action_estimate_shipping(self): - # if self.carrier_id.id in [1, 151]: - # self.action_indoteknik_estimate_shipping() - # return - - # total_weight = 0 - # missing_weight_products = [] - - # for line in self.order_line: - # if line.weight > 0: - # total_weight += line.weight * line.product_uom_qty - # line.product_id.weight = line.weight - # else: - # missing_weight_products.append(line.product_id.name) - - # if missing_weight_products: - # product_names = '
'.join(missing_weight_products) - # self.message_post(body=f"Produk berikut tidak memiliki berat:
{product_names}") - - # if total_weight == 0: - # raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") - - # destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id - # if not destination_subsdistrict_id: - # raise UserError("Gagal mendapatkan ID kota tujuan.") - - # result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id) - # if result: - # shipping_options = [] - # for courier in result['rajaongkir']['results']: - # for cost_detail in courier['costs']: - # service = cost_detail['service'] - # description = cost_detail['description'] - # etd = cost_detail['cost'][0]['etd'] - # value = cost_detail['cost'][0]['value'] - # shipping_options.append((service, description, etd, value, courier['code'])) - - # self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() - - # for service, description, etd, value, provider in shipping_options: - # self.env["shipping.option"].create({ - # "name": service, - # "price": value, - # "provider": provider, - # "etd": etd, - # "sale_order_id": self.id, - # }) - - # self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id - - # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
Detail Lain:
{'
'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}") - # else: - # raise UserError("Gagal mendapatkan estimasi ongkir.") def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' -- cgit v1.2.3 From 0a5e1713cee2cec10863c77fe930e8985da62d16 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 16 Apr 2025 11:02:00 +0700 Subject: push --- indoteknik_custom/models/logbook_sj.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/logbook_sj.py b/indoteknik_custom/models/logbook_sj.py index 9f349882..75b2622f 100644 --- a/indoteknik_custom/models/logbook_sj.py +++ b/indoteknik_custom/models/logbook_sj.py @@ -26,6 +26,8 @@ class LogbookSJ(models.TransientModel): report_logbook = self.env['report.logbook.sj'].create([parameters_header]) for line in logbook_line: picking = self.env['stock.picking'].search([('picking_code', '=', line.name)], limit=1) + if not picking: + picking = self.env['stock.picking'].search([('out_code', '=', line.name)], limit=1) stock = picking parent_id = stock.partner_id.parent_id.id parent_id = parent_id if parent_id else stock.partner_id.id @@ -80,6 +82,9 @@ class LogbookSJLine(models.TransientModel): if len(self.name) == 13: self.name = self.name[:-1] picking = self.env['stock.picking'].search([('picking_code', '=', self.name)], limit=1) + + if not picking: + picking = self.env['stock.picking'].search([('out_code', '=', self.name)], limit=1) if picking: if picking.driver_id: self.driver_id = picking.driver_id -- cgit v1.2.3 From 6ee875d5dad299ebcd17c16d6c3736cf881e53f4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 16 Apr 2025 11:30:28 +0700 Subject: ppush --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c83ffd61..97e3b5b0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -472,7 +472,7 @@ class SaleOrder(models.Model): def _compute_date_kirim(self): for rec in self: - picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel'])], order='date_doc_kirim desc', limit=1) + picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')], order='date_doc_kirim desc', limit=1) rec.date_kirim_ril = picking.date_doc_kirim rec.date_status_done = picking.date_done rec.date_driver_arrival = picking.driver_arrival_date -- cgit v1.2.3 From 0ee640e0030441c204be6de3edc4184a37c85cd8 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 16 Apr 2025 11:46:43 +0700 Subject: (andri) add min & max NPWP --- indoteknik_custom/models/res_partner.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index fd3a0514..04ee136c 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -104,7 +104,7 @@ class ResPartner(models.Model): ('nonpkp', 'Non PKP') ]) sppkp = fields.Char(string="SPPKP", tracking=True) - npwp = fields.Char(string="NPWP", tracking=True) + npwp = fields.Char(string="npwp", tracking=True) nitku = fields.Char(string="NITKU", tracking=True) counter = fields.Integer(string="Counter", default=0) leadtime = fields.Integer(string="Leadtime", default=0) @@ -200,6 +200,17 @@ class ResPartner(models.Model): if existing_partner: raise ValidationError(f"Nama '{record.name}' sudah digunakan oleh partner lain!") + @api.constrains('npwp') + def _check_npwp(self): + for rec in self: + if rec.npwp: + if not rec.npwp.isdigit(): + raise ValidationError("NPWP hanya boleh berisi angka.") + if len(rec.npwp) <= 15: + raise UserError("NPWP terlalu pendek. Minimal 15 digit.") + if len(rec.npwp) >= 16: + raise ValidationError("NPWP terlalu panjang. Maksimal 16 digit.") + def write(self, vals): # Fungsi rekursif untuk meng-update semua child, termasuk child dari child def update_children_recursively(partner, vals_for_child): -- cgit v1.2.3 From e2a9f4f82b0c761cc4c20d501ab586239c0126a3 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 16 Apr 2025 11:51:03 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 383c75a3..3aa3a0a4 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1542,9 +1542,22 @@ class CheckProduct(models.Model): index=True, copy=False, ) - product_id = fields.Many2one('product.product', string='Product', required=True) - quantity = fields.Float(string='Quantity', default=1.0, required=True) + product_id = fields.Many2one('product.product', string='Product') + quantity = fields.Float(string='Quantity', default=1.0) status = fields.Char(string='Status', compute='_compute_status') + code_product = fields.Char(string='Code Product') + + @api.onchange('code_product') + def _onchange_code_product(self): + if self.code_product: + product = self.env['product.product'].search([('default_code', '=', self.code_product)], limit=1) + if not product: + product = self.env['product.product'].search([('barcode', '=', self.code_product)], limit=1) + + if product: + self.product_id = product.id + else: + raise UserError("Product tidak ditemukan") @api.depends('quantity') def _compute_status(self): @@ -1654,13 +1667,13 @@ class CheckProduct(models.Model): # Calculate the total quantity after addition total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity - if total_quantity > total_qty_in_moves: + if total_quantity == total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total - if record.quantity > total_qty_in_moves: + if record.quantity == total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) @@ -1741,6 +1754,16 @@ class ScanKoli(models.Model): string="Progress Scan Koli", compute="_compute_scan_koli_progress" ) + code_koli = fields.Char(string='Code Koli') + + @api.onchange('code_koli') + def _onchange_code_koli(self): + if self.code_koli: + koli = self.env['sales.order.koli'].search([('koli_id.koli', '=', self.code_koli)], limit=1) + if koli: + self.write({'koli_id': koli.id}) + else: + raise UserError('Koli tidak ditemukan') def _compute_scan_koli_progress(self): for scan in self: -- cgit v1.2.3 From e903492969baf706e73fca2a2a85168dfb815219 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 16 Apr 2025 14:58:43 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 34 +++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3aa3a0a4..85428aa0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1765,14 +1765,14 @@ class ScanKoli(models.Model): else: raise UserError('Koli tidak ditemukan') - def _compute_scan_koli_progress(self): - for scan in self: - if scan.picking_id: - all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') - if all_scans: - scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan - total_so_koli = scan.picking_id.total_so_koli - scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" + # def _compute_scan_koli_progress(self): + # for scan in self: + # if scan.picking_id: + # all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') + # if all_scans: + # scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan + # total_so_koli = scan.picking_id.total_so_koli + # scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" @api.onchange('koli_id') def _onchange_koli_compare_with_konfirm_koli(self): @@ -1844,13 +1844,21 @@ class ScanKoli(models.Model): def _compute_scan_koli_progress(self): for scan in self: - if scan.picking_id: + if not scan.picking_id: + scan.scan_koli_progress = "0/0" + continue + + try: all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') if all_scans: - scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan - total_so_koli = scan.picking_id.total_so_koli - scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" - + scan_index = list(all_scans).index(scan) + 1 + total_so_koli = scan.picking_id.total_so_koli or 0 + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" + else: + scan.scan_koli_progress = "0/0" + except Exception: + # Fallback in case of any error + scan.scan_koli_progress = "0/0" @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): for scan in self.picking_id.scan_koli_lines: -- cgit v1.2.3 From 30f5cdf7b2537093d9759c49ba027ffd0608acb9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 17 Apr 2025 08:38:17 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 85428aa0..97607502 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1667,13 +1667,13 @@ class CheckProduct(models.Model): # Calculate the total quantity after addition total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity - if total_quantity == total_qty_in_moves: + if total_quantity > total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total - if record.quantity == total_qty_in_moves: + if record.quantity > total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) -- cgit v1.2.3 From de6450a17a85c063fe49705991098116efccae75 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 17 Apr 2025 10:49:27 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 97607502..85428aa0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1667,13 +1667,13 @@ class CheckProduct(models.Model): # Calculate the total quantity after addition total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity - if total_quantity > total_qty_in_moves: + if total_quantity == total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total - if record.quantity > total_qty_in_moves: + if record.quantity == total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) -- cgit v1.2.3 From 984c3fe0d032dc0e37aae030b10658c310b9705d Mon Sep 17 00:00:00 2001 From: AndriFP Date: Thu, 17 Apr 2025 13:50:10 +0700 Subject: (andri) Check + revisi add minimum dev amt & add log note after estimate shipping --- indoteknik_custom/models/res_partner.py | 16 ++--- indoteknik_custom/models/sale_order.py | 108 +++++++++++++++----------------- 2 files changed, 60 insertions(+), 64 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 04ee136c..451577c5 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -104,7 +104,7 @@ class ResPartner(models.Model): ('nonpkp', 'Non PKP') ]) sppkp = fields.Char(string="SPPKP", tracking=True) - npwp = fields.Char(string="npwp", tracking=True) + npwp = fields.Char(string="NPWP", tracking=True) nitku = fields.Char(string="NITKU", tracking=True) counter = fields.Integer(string="Counter", default=0) leadtime = fields.Integer(string="Leadtime", default=0) @@ -202,14 +202,14 @@ class ResPartner(models.Model): @api.constrains('npwp') def _check_npwp(self): - for rec in self: - if rec.npwp: - if not rec.npwp.isdigit(): + for record in self: + if record.npwp: + if not record.npwp.isdigit(): raise ValidationError("NPWP hanya boleh berisi angka.") - if len(rec.npwp) <= 15: - raise UserError("NPWP terlalu pendek. Minimal 15 digit.") - if len(rec.npwp) >= 16: - raise ValidationError("NPWP terlalu panjang. Maksimal 16 digit.") + if len(record.npwp) < 15: + raise ValidationError("Digit NPWP yang dimasukkan kurang dari batas minimal (15 digit)") + if len(record.npwp) > 16: + raise ValidationError("Digit NPWP yang dimasukkan lebih dari batas maksimal (16 digit)") def write(self, vals): # Fungsi rekursif untuk meng-update semua child, termasuk child dari child diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index cc7b9851..92581678 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -327,10 +327,6 @@ class SaleOrder(models.Model): ) def action_estimate_shipping(self): - # Pengecekan Minimum Delivery Amount - if self.delivery_amt < 5000: - raise UserError("Estimasi ongkos kirim belum mencapai jumlah minimum untuk pengiriman") - if self.carrier_id.id in [1, 151]: self.action_indoteknik_estimate_shipping() return @@ -357,50 +353,45 @@ class SaleOrder(models.Model): raise UserError("Gagal mendapatkan ID kota tujuan.") result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id) + if result: + shipping_options = [] + for courier in result['rajaongkir']['results']: + for cost_detail in courier['costs']: + service = cost_detail['service'] + description = cost_detail['description'] + etd = cost_detail['cost'][0]['etd'] + value = cost_detail['cost'][0]['value'] + shipping_options.append((service, description, etd, value, courier['code'])) + + self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() - if not result: - raise UserError("Estimasi Ongkir gagal sebab pilihan kurir tidak terdaftar di RajaOngkir") - - shipping_options = [] - for courier in result['rajaongkir']['results']: - for cost_detail in courier['costs']: - service = cost_detail['service'] - description = cost_detail['description'] - etd = cost_detail['cost'][0]['etd'] - value = cost_detail['cost'][0]['value'] - shipping_options.append((service, description, etd, value, courier['code'])) - - # Hapus opsi shipping lama - self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() - - # Simpan opsi shipping baru - for service, description, etd, value, provider in shipping_options: - self.env["shipping.option"].create({ - "name": service, - "price": value, - "provider": provider, - "etd": etd, - "sale_order_id": self.id, - }) + _logger.info(f"Shipping options: {shipping_options}") + + for service, description, etd, value, provider in shipping_options: + self.env["shipping.option"].create({ + "name": service, + "price": value, + "provider": provider, + "etd": etd, + "sale_order_id": self.id, + }) + + + self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id + + _logger.info(f"Shipping option SO ID: {self.shipping_option_id}") - # Set opsi shipping default - first_option = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1) - if first_option: - self.shipping_option_id = first_option.id - message = ( - f"Estimasi pengiriman berhasil:
" - f"Layanan: {first_option.name}
" - f"Deskripsi: {dict(first_option._fields['name'].selection).get(first_option.name, first_option.name)}
" - f"ETD: {first_option.etd} hari
" - f"Biaya: Rp {first_option.price:,}" - ) self.message_post( - body=message, - message_type="comment", + body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
Detail Lain:
" + f"{'
'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", + message_type="comment" ) + + # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
Detail Lain:
{'
'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment") + else: raise UserError("Gagal mendapatkan estimasi ongkir.") - + def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' @@ -702,26 +693,31 @@ class SaleOrder(models.Model): raise UserError('Email yang anda input kurang valid') # @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered') - def _validate_delivery_amt(self): - if self.delivery_amt < 5000: - raise UserError("Estimasi Ongkos Kirim belum memenuhi jumlah minimum untuk pengiriman.") + is_indoteknik = self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik' + is_active_id = not self.env.context.get('active_id', []) - if self.delivery_amt < 1: - if (self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): + if is_indoteknik and is_active_id: + if self.delivery_amt == 0: if self.carrier_id.id == 1: - raise UserError('Untuk Kurir Indoteknik Delivery, Estimasi Ongkos Kirim Harus di isi') + raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum diisi.') else: - raise UserError('Untuk Shipping Covered Indoteknik, Estimasi Ongkos Kirim Harus di isi') + raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.') + + if self.delivery_amt < 5000: + if self.carrier_id.id == 1: + raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.') + else: + raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.') + + # if self.delivery_amt < 5000: + # if (self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): + # if self.carrier_id.id == 1: + # raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi jumlah minimum.') + # else: + # raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi jumlah minimum.') - # def _validate_delivery_amt(self): - # if self.delivery_amt < 1: - # if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): - # if(self.carrier_id.id == 1): - # raise UserError('Untuk Kurir Indoteknik Delivery, Estimasi Ongkos Kirim Harus di isi') - # else: - # raise UserError('Untuk Shipping Covered Indoteknik, Estimasi Ongkos Kirim Harus di isi') def override_allow_create_invoice(self): if not self.env.user.is_accounting: -- cgit v1.2.3 From fcb65326ffd7daf6136a97b53cca5493964d94aa Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 17 Apr 2025 17:06:30 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 85428aa0..f04b0e79 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1559,6 +1559,34 @@ class CheckProduct(models.Model): else: raise UserError("Product tidak ditemukan") + def unlink(self): + # Get all affected pickings before deletion + pickings = self.mapped('picking_id') + + # Store product_ids that will be deleted + deleted_product_ids = self.mapped('product_id') + + # Perform the deletion + result = super(CheckProduct, self).unlink() + + # After deletion, update moves for affected pickings + for picking in pickings: + # For products that were completely removed (no remaining check.product lines) + remaining_product_ids = picking.check_product_lines.mapped('product_id') + removed_product_ids = deleted_product_ids - remaining_product_ids + + # Set quantity_done to 0 for moves of completely removed products + moves_to_reset = picking.move_ids_without_package.filtered( + lambda move: move.product_id in removed_product_ids + ) + for move in moves_to_reset: + move.quantity_done = 0.0 + + # Also sync remaining products in case their totals changed + self._sync_check_product_to_moves(picking) + + return result + @api.depends('quantity') def _compute_status(self): for record in self: -- cgit v1.2.3 From 7545da8fdc9fc01fe9b2bd2a4612ae22d0be8e57 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 19 Apr 2025 11:59:01 +0700 Subject: add constrains date doc kirim to update calculate line no --- indoteknik_custom/models/stock_picking.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f04b0e79..923c56f5 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -248,6 +248,11 @@ class StockPicking(models.Model): final_seq = fields.Float(string='Remaining Time') shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') + + @api.constrains('date_doc_kirim') + def _constrains_date_doc_kirim(self): + for rec in self: + rec.calculate_line_no() @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): @@ -1091,7 +1096,6 @@ class StockPicking(models.Model): if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name: self.check_koli() res = super(StockPicking, self).button_validate() - self.calculate_line_no() self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' self.final_seq = 0 -- cgit v1.2.3 From ad139f4c1613f4571074006276d29448bfe95ff8 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 21 Apr 2025 14:11:46 +0700 Subject: (andrifp) fix validate NPWP --- indoteknik_custom/models/res_partner.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 451577c5..06945301 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -39,6 +39,10 @@ class ResPartner(models.Model): estimasi_tempo = fields.Char(string='Estimasi Pembelian Pertahun') tempo_duration = fields.Many2one('account.payment.term', string='Durasi Tempo') tempo_limit = fields.Char(string='Limit Tempo') + minimum_amount = fields.Float( + string="Minimum Order", + help="Jika total belanja kurang dari ini, maka payment term akan otomatis menjadi CBD." + ) category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan', domain=lambda self: self._get_default_category_domain()) @api.model @@ -203,13 +207,14 @@ class ResPartner(models.Model): @api.constrains('npwp') def _check_npwp(self): for record in self: - if record.npwp: - if not record.npwp.isdigit(): - raise ValidationError("NPWP hanya boleh berisi angka.") - if len(record.npwp) < 15: - raise ValidationError("Digit NPWP yang dimasukkan kurang dari batas minimal (15 digit)") - if len(record.npwp) > 16: - raise ValidationError("Digit NPWP yang dimasukkan lebih dari batas maksimal (16 digit)") + npwp = record.npwp.strip() if record.npwp else '' + # Abaikan validasi jika NPWP kosong atau diisi "0" + if not npwp or npwp == '0' or npwp == '00.000.000.0-000.000': + continue + if len(npwp) < 15: + raise ValidationError("Digit NPWP yang dimasukkan kurang dari batas minimal (15 digit)") + if len(npwp) > 16: + raise ValidationError("Digit NPWP yang dimasukkan lebih dari batas maksimal (16 digit)") def write(self, vals): # Fungsi rekursif untuk meng-update semua child, termasuk child dari child @@ -476,6 +481,8 @@ class ResPartner(models.Model): def _onchange_customer_type(self): if self.customer_type == 'nonpkp': self.npwp = '00.000.000.0-000.000' + elif self.customer_type == 'pkp': + self.npwp = '00.000.000.0-000.000' def get_check_payment_term(self): self.ensure_one() -- cgit v1.2.3 From cd51ac345b0898034428aab3b8ded24d03c0fdfd Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 15:06:14 +0700 Subject: approval invoice date --- indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/approval_invoice_date.py | 46 +++++++++++++++++++++++ indoteknik_custom/models/stock_picking.py | 40 +++++++++++++++++++- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 indoteknik_custom/models/approval_invoice_date.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 935a0aa0..08fa9803 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -149,3 +149,4 @@ from . import sales_order_koli from . import stock_backorder_confirmation from . import account_payment_register from . import stock_inventory +from . import approval_invoice_date diff --git a/indoteknik_custom/models/approval_invoice_date.py b/indoteknik_custom/models/approval_invoice_date.py new file mode 100644 index 00000000..48546e55 --- /dev/null +++ b/indoteknik_custom/models/approval_invoice_date.py @@ -0,0 +1,46 @@ +from odoo import models, api, fields +from odoo.exceptions import AccessError, UserError, ValidationError +from datetime import timedelta, date, datetime +import logging + +_logger = logging.getLogger(__name__) + +class ApprovalInvoiceDate(models.Model): + _name = "approval.invoice.date" + _description = "Approval Invoice Date" + _rec_name = 'number' + + picking_id = fields.Many2one('stock.picking', string='Picking') + number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True) + date_invoice = fields.Datetime( + string='Invoice Date', + copy=False + ) + date_doc_do = fields.Datetime( + string='Tanggal Kirim di SJ', + copy=False + ) + state = fields.Selection([('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Cancel')], string='State', default='draft', tracking=True) + approve_date = fields.Datetime(string='Approve Date', copy=False) + approve_by = fields.Many2one('res.users', string='Approve By', copy=False) + sale_id = fields.Many2one('sale.order', string='Sale Order') + partner_id = fields.Many2one('res.partner', string='Partner') + move_id = fields.Many2one('account.move', string='Invoice') + note = fields.Char(string='Note') + + def button_approve(self): + if not self.env.user.is_accounting: + raise UserError("Hanya Accounting Yang Bisa Approve") + self.move_id.invoice_date = self.date_doc_do + self.state = 'done' + self.approve_date = datetime.utcnow() + self.approve_by = self.env.user.id + + def button_cancel(self): + self.state = 'cancel' + + @api.model + def create(self, vals): + vals['number'] = self.env['ir.sequence'].next_by_code('approval.invoice.date') or '0' + result = super(ApprovalInvoiceDate, self).create(vals) + return result diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 923c56f5..0701a989 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -248,11 +248,40 @@ class StockPicking(models.Model): final_seq = fields.Float(string='Remaining Time') shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') + approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') @api.constrains('date_doc_kirim') def _constrains_date_doc_kirim(self): for rec in self: rec.calculate_line_no() + + invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id)], limit=1, order='create_date desc') + + if invoice: + if rec.date_doc_kirim != invoice.invoice_date: + get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) + + if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': + get_approval_invoice_date.date_doc_do = rec.date_doc_kirim + else: + approval_invoice_date = self.env['approval.invoice.date'].create({ + 'picking_id': rec.id, + 'date_invoice': invoice.invoice_date, + 'date_doc_do': rec.date_doc_kirim, + 'sale_id': rec.sale_id.id, + 'move_id': invoice.id, + 'partner_id': rec.partner_id.id + }) + + rec.approval_invoice_date_id = approval_invoice_date.id + + if approval_invoice_date: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, + } + @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): @@ -1002,6 +1031,8 @@ class StockPicking(models.Model): raise UserError('Hanya MD yang bisa Approve') def button_validate(self): + self.check_invoice_date() + threshold_datetime = waktu(2025, 4, 11, 6, 26) group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) active_model = self.env.context.get('active_model') @@ -1013,7 +1044,6 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - threshold_datetime = waktu(2025, 4, 11, 6, 26) if (len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name @@ -1129,6 +1159,14 @@ class StockPicking(models.Model): self.send_mail_bills() return res + def check_invoice_date(self): + for picking in self: + invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)]) + + if invoice: + if picking.date_doc_kirim.date() != invoice.invoice_date: + raise UserError("Tanggal Kirim tidak sesuai dengan Invoice") + def set_picking_code_out(self): for picking in self: # Check if picking meets criteria -- cgit v1.2.3 From d5d073f342727e8440884b462b5d3a589894f296 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 15:28:48 +0700 Subject: validation tanggal kirim di sj --- indoteknik_custom/models/stock_picking.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0701a989..62765099 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -250,6 +250,20 @@ class StockPicking(models.Model): state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') + def _check_date_doc_kirim_modification(self): + for record in self: + if record.date_doc_kirim: + kirim_date = fields.Datetime.from_string(record.date_doc_kirim) + now = fields.Datetime.now() + + deadline = kirim_date + timedelta(days=1) + deadline = deadline.replace(hour=10, minute=0, second=0) + + if now > deadline: + raise ValidationError( + _("Anda tidak dapat mengubah Tanggal Kirim setelah jam 10:00 pada hari berikutnya!") + ) + @api.constrains('date_doc_kirim') def _constrains_date_doc_kirim(self): for rec in self: @@ -258,6 +272,7 @@ class StockPicking(models.Model): invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id)], limit=1, order='create_date desc') if invoice: + rec._check_date_doc_kirim_modification() if rec.date_doc_kirim != invoice.invoice_date: get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) -- cgit v1.2.3 From 286d1c7d91b3e957e26a2ef1350a80faba9405ed Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 21 Apr 2025 15:40:54 +0700 Subject: (andrifp) fix validate NPWP format 15 digit --- indoteknik_custom/models/res_partner.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 06945301..84edafea 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -2,6 +2,7 @@ from odoo import models, fields, api from odoo.exceptions import UserError, ValidationError from datetime import datetime from odoo.http import request +import re class GroupPartner(models.Model): _name = 'group.partner' @@ -211,10 +212,24 @@ class ResPartner(models.Model): # Abaikan validasi jika NPWP kosong atau diisi "0" if not npwp or npwp == '0' or npwp == '00.000.000.0-000.000': continue - if len(npwp) < 15: - raise ValidationError("Digit NPWP yang dimasukkan kurang dari batas minimal (15 digit)") - if len(npwp) > 16: - raise ValidationError("Digit NPWP yang dimasukkan lebih dari batas maksimal (16 digit)") + + # Validasi untuk NPWP 15 digit (format: 99.999.999.9-999.999) + if len(npwp) == 20: + # Regex untuk 15 digit dengan format titik dan tanda hubung + pattern_15_digit = r'^\d{2}\.\d{3}\.\d{3}\.\d{1}-\d{3}\.\d{3}$' + if not re.match(pattern_15_digit, npwp): + raise ValidationError("Format NPWP 15 digit yang dimasukkan salah. Pastikan format yang benar adalah: 99.999.999.9-999.999") + + # Validasi untuk NPWP 16 digit (hanya angka tanpa titik atau tanda hubung) + elif len(npwp) == 16: + pattern_16_digit = r'^\d{16}$' + if not re.match(pattern_16_digit, npwp): + raise ValidationError("Format NPWP 16 digit yang dimasukkan salah. Format yang benar adalah 16 digit angka tanpa titik atau tanda hubung.") + + # Validasi panjang NPWP jika lebih atau kurang dari 15 atau 16 digit + else: + raise ValidationError("Digit NPWP yang dimasukkan tidak sesuai. Pastikan NPWP memiliki 15 digit dengan format tertentu (99.999.999.9-999.999) atau 16 digit tanpa tanda hubung.") + def write(self, vals): # Fungsi rekursif untuk meng-update semua child, termasuk child dari child -- cgit v1.2.3 From a4abe61f52162709b7e4f9f88edfc482e092c517 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 15:41:13 +0700 Subject: validasi compare total_mapping_koli and total_scan_koli --- indoteknik_custom/models/stock_picking.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 62765099..95591c35 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -303,6 +303,9 @@ class StockPicking(models.Model): now = datetime.datetime.utcnow() for picking in self: if len(picking.scan_koli_lines) > 0: + if len(picking.scan_koli_lines) != picking.total_mapping_koli: + raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli") + picking.driver_departure_date = now @api.depends('total_so_koli') -- cgit v1.2.3 From ace97e462fc9376b041ee8b45e77f55b8d4b7837 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 16:04:22 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 95591c35..495a9d11 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1179,11 +1179,12 @@ class StockPicking(models.Model): def check_invoice_date(self): for picking in self: - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)]) + if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name: + invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)]) - if invoice: - if picking.date_doc_kirim.date() != invoice.invoice_date: - raise UserError("Tanggal Kirim tidak sesuai dengan Invoice") + if invoice: + if picking.date_doc_kirim.date() != invoice.invoice_date: + raise UserError("Tanggal Kirim tidak sesuai dengan Invoice") def set_picking_code_out(self): for picking in self: -- cgit v1.2.3 From 758ce73a0f2bf8cf347d8e655954677d76349027 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 16:10:11 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 495a9d11..977b79c0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1180,11 +1180,26 @@ class StockPicking(models.Model): def check_invoice_date(self): for picking in self: if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name: - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)]) - - if invoice: - if picking.date_doc_kirim.date() != invoice.invoice_date: - raise UserError("Tanggal Kirim tidak sesuai dengan Invoice") + continue # Skip non-relevant pickings + + invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)], limit=1) + + if not invoice: + continue # Skip if no invoice found + + # Check if both dates exist + if not picking.date_doc_kirim or not invoice.invoice_date: + raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") + + # Convert to date objects for comparison + picking_date = fields.Date.to_date(picking.date_doc_kirim) + invoice_date = fields.Date.to_date(invoice.invoice_date) + + if picking_date != invoice_date: + raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % ( + picking_date.strftime('%d-%m-%Y'), + invoice_date.strftime('%d-%m-%Y') + )) def set_picking_code_out(self): for picking in self: -- cgit v1.2.3 From b1f520b1b3b100deea99980b5781744411fe6936 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 22 Apr 2025 09:35:32 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 977b79c0..a860dd5d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -249,11 +249,12 @@ class StockPicking(models.Model): shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') + last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') def _check_date_doc_kirim_modification(self): for record in self: - if record.date_doc_kirim: - kirim_date = fields.Datetime.from_string(record.date_doc_kirim) + if record.last_update_date_doc_kirim: + kirim_date = fields.Datetime.from_string(record.last_update_date_doc_kirim) now = fields.Datetime.now() deadline = kirim_date + timedelta(days=1) @@ -269,9 +270,9 @@ class StockPicking(models.Model): for rec in self: rec.calculate_line_no() - invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id)], limit=1, order='create_date desc') + invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') - if invoice: + if invoice and not self.env.context.get('active_model') == 'stock.picking': rec._check_date_doc_kirim_modification() if rec.date_doc_kirim != invoice.invoice_date: get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) @@ -296,6 +297,8 @@ class StockPicking(models.Model): 'tag': 'display_notification', 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, } + + rec.last_update_date_doc_kirim = fields.Datetime.now() @api.constrains('scan_koli_lines') @@ -1180,18 +1183,16 @@ class StockPicking(models.Model): def check_invoice_date(self): for picking in self: if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name: - continue # Skip non-relevant pickings + continue invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)], limit=1) if not invoice: - continue # Skip if no invoice found + continue - # Check if both dates exist if not picking.date_doc_kirim or not invoice.invoice_date: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") - # Convert to date objects for comparison picking_date = fields.Date.to_date(picking.date_doc_kirim) invoice_date = fields.Date.to_date(invoice.invoice_date) -- cgit v1.2.3 From 19e32a22d394e0597278ee7e1c1373d7cb799ff5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 22 Apr 2025 10:10:58 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 57 ++++++++++++++++--------------- 1 file changed, 29 insertions(+), 28 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a860dd5d..95e0d59f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -270,35 +270,36 @@ class StockPicking(models.Model): for rec in self: rec.calculate_line_no() - invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') - - if invoice and not self.env.context.get('active_model') == 'stock.picking': - rec._check_date_doc_kirim_modification() - if rec.date_doc_kirim != invoice.invoice_date: - get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) - - if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': - get_approval_invoice_date.date_doc_do = rec.date_doc_kirim - else: - approval_invoice_date = self.env['approval.invoice.date'].create({ - 'picking_id': rec.id, - 'date_invoice': invoice.invoice_date, - 'date_doc_do': rec.date_doc_kirim, - 'sale_id': rec.sale_id.id, - 'move_id': invoice.id, - 'partner_id': rec.partner_id.id - }) - - rec.approval_invoice_date_id = approval_invoice_date.id - - if approval_invoice_date: - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, - } + if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name: + invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') + + if invoice and not self.env.context.get('active_model') == 'stock.picking': + rec._check_date_doc_kirim_modification() + if rec.date_doc_kirim != invoice.invoice_date: + get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) + + if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': + get_approval_invoice_date.date_doc_do = rec.date_doc_kirim + else: + approval_invoice_date = self.env['approval.invoice.date'].create({ + 'picking_id': rec.id, + 'date_invoice': invoice.invoice_date, + 'date_doc_do': rec.date_doc_kirim, + 'sale_id': rec.sale_id.id, + 'move_id': invoice.id, + 'partner_id': rec.partner_id.id + }) + + rec.approval_invoice_date_id = approval_invoice_date.id + + if approval_invoice_date: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, + } - rec.last_update_date_doc_kirim = fields.Datetime.now() + rec.last_update_date_doc_kirim = fields.Datetime.now() @api.constrains('scan_koli_lines') -- cgit v1.2.3 From fc5ed4aab391eb3ec09cae6179209fe30c1bc57f Mon Sep 17 00:00:00 2001 From: AndriFP Date: Tue, 22 Apr 2025 10:57:24 +0700 Subject: (andri) add min amount vendor payment --- indoteknik_custom/models/purchase_order.py | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index b107f389..5e9e509f 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -89,6 +89,39 @@ class PurchaseOrder(models.Model): store_name = fields.Char(string='Nama Toko') purchase_order_count = fields.Integer('Purchase Order Count', related='partner_id.purchase_order_count') + # cek payment term + def _check_payment_term(self): + _logger.info("Check Payment Term Terpanggil") + + cbd_term = self.env['account.payment.term'].search([ + ('name', 'ilike', 'Cash Before Delivery') + ], limit=1) + + for order in self: + if not order.partner_id or not order.partner_id.minimum_amount: + continue + + if not order.order_line or order.amount_total == 0: + continue + + if order.amount_total < order.partner_id.minimum_amount: + if cbd_term and order.payment_term_id != cbd_term: + order.payment_term_id = cbd_term.id + self.env.user.notify_info( + message="Total belanja PO belum mencapai minimum yang ditentukan vendor. " + "Payment Term telah otomatis diubah menjadi Cash Before Delivery (C.B.D).", + title="Payment Term Diperbarui" + ) + else: + vendor_term = order.partner_id.property_supplier_payment_term_id + if vendor_term and order.payment_term_id != vendor_term: + order.payment_term_id = vendor_term.id + self.env.user.notify_info( + message=f"Total belanja PO telah memenuhi jumlah minimum vendor. " + f"Payment Term otomatis dikembalikan ke pengaturan vendor awal: *{vendor_term.name}*.", + title="Payment Term Diperbarui" + ) + @api.onchange('total_cost_service') def _onchange_total_cost_service(self): for order in self: @@ -672,6 +705,7 @@ class PurchaseOrder(models.Model): raise UserError("Produk "+line.product_id.name+" memiliki vendor berbeda dengan SO (Vendor PO: "+str(self.partner_id.name)+", Vendor SO: "+str(line.so_line_id.vendor_id.name)+")") def button_confirm(self): + # self._check_payment_term() # check payment term res = super(PurchaseOrder, self).button_confirm() current_time = datetime.now() self.check_ppn_mix() @@ -1079,6 +1113,17 @@ class PurchaseOrder(models.Model): return super(PurchaseOrder, self).button_unlock() + @api.model #override custom create & write for check payment term + def create(self, vals): + order = super().create(vals) + order.with_context(skip_check_payment=True)._check_payment_term() + return order + + def write(self, vals): + res = super().write(vals) + if not self.env.context.get('skip_check_payment'): + self.with_context(skip_check_payment=True)._check_payment_term() + return res class PurchaseOrderUnlockWizard(models.TransientModel): _name = 'purchase.order.unlock.wizard' -- cgit v1.2.3 From bdf8bf07485b01e26f61cbc75578d754af4439e8 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 22 Apr 2025 11:58:44 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 95e0d59f..96aac7a1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -270,7 +270,7 @@ class StockPicking(models.Model): for rec in self: rec.calculate_line_no() - if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name: + if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name and rec.partner_id.id != 96868: invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') if invoice and not self.env.context.get('active_model') == 'stock.picking': @@ -299,7 +299,7 @@ class StockPicking(models.Model): 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, } - rec.last_update_date_doc_kirim = fields.Datetime.now() + rec.last_update_date_doc_kirim = datetime.datetime.utcnow() @api.constrains('scan_koli_lines') @@ -1183,7 +1183,7 @@ class StockPicking(models.Model): def check_invoice_date(self): for picking in self: - if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name: + if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name or picking.partner_id.id == 96868: continue invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)], limit=1) -- cgit v1.2.3 From 2f16e84dcceec98e3dd49cbbbf57457d00989a04 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Tue, 22 Apr 2025 12:28:04 +0700 Subject: (miqdad) add xpdc --- indoteknik_custom/models/sale_order.py | 79 +++++++++++++--------------------- 1 file changed, 29 insertions(+), 50 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 92581678..2061c686 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -67,6 +67,17 @@ class ShippingOption(models.Model): class SaleOrder(models.Model): _inherit = "sale.order" + ongkir_ke_xpdc = fields.Float(string='Ongkir ke Ekspedisi', help='Biaya ongkir ekspedisi', copy=False, index=True, tracking=3) + + metode_kirim_ke_xpdc = fields.Selection([ + ('indoteknik_deliv', 'Indoteknik Delivery'), + ('lalamove', 'Lalamove'), + ('grab', 'Grab'), + ('gojek', 'Gojek'), + ('deliveree', 'Deliveree'), + ('other', 'Other'), + ], string='Metode Kirim Ke Ekspedisi', copy=False, index=True, tracking=3) + koli_lines = fields.One2many('sales.order.koli', 'sale_order_id', string='Sales Order Koli', auto_join=True) fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2') fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment') @@ -315,17 +326,7 @@ class SaleOrder(models.Model): "sale_order_id": self.id, }) self.shipping_option_id = shipping_option.id - self.message_post( - body=( - f"Estimasi pengiriman Indoteknik berhasil:
" - f"Layanan: {shipping_option.name}
" - f"ETD: {shipping_option.etd}
" - f"Biaya: Rp {shipping_option.price:,}
" - f"Provider: {shipping_option.provider}" - ), - message_type="comment", - ) - + def action_estimate_shipping(self): if self.carrier_id.id in [1, 151]: self.action_indoteknik_estimate_shipping() @@ -364,8 +365,6 @@ class SaleOrder(models.Model): shipping_options.append((service, description, etd, value, courier['code'])) self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() - - _logger.info(f"Shipping options: {shipping_options}") for service, description, etd, value, provider in shipping_options: self.env["shipping.option"].create({ @@ -375,23 +374,12 @@ class SaleOrder(models.Model): "etd": etd, "sale_order_id": self.id, }) - self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id - _logger.info(f"Shipping option SO ID: {self.shipping_option_id}") - - self.message_post( - body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
Detail Lain:
" - f"{'
'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", - message_type="comment" - ) - - # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
Detail Lain:
{'
'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment") - + self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
Detail Lain:
{'
'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}") else: raise UserError("Gagal mendapatkan estimasi ongkir.") - def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' @@ -495,7 +483,7 @@ class SaleOrder(models.Model): def _compute_date_kirim(self): for rec in self: - picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel'])], order='date_doc_kirim desc', limit=1) + picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')], order='date_doc_kirim desc', limit=1) rec.date_kirim_ril = picking.date_doc_kirim rec.date_status_done = picking.date_done rec.date_driver_arrival = picking.driver_arrival_date @@ -694,30 +682,12 @@ class SaleOrder(models.Model): # @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered') def _validate_delivery_amt(self): - is_indoteknik = self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik' - is_active_id = not self.env.context.get('active_id', []) - - if is_indoteknik and is_active_id: - if self.delivery_amt == 0: - if self.carrier_id.id == 1: - raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum diisi.') - else: - raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.') - - if self.delivery_amt < 5000: - if self.carrier_id.id == 1: - raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.') + if self.delivery_amt < 1: + if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): + if(self.carrier_id.id == 1): + raise UserError('Untuk Kurir Indoteknik Delivery, Estimasi Ongkos Kirim Harus di isi') else: - raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.') - - - # if self.delivery_amt < 5000: - # if (self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): - # if self.carrier_id.id == 1: - # raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi jumlah minimum.') - # else: - # raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi jumlah minimum.') - + raise UserError('Untuk Shipping Covered Indoteknik, Estimasi Ongkos Kirim Harus di isi') def override_allow_create_invoice(self): if not self.env.user.is_accounting: @@ -1467,12 +1437,21 @@ class SaleOrder(models.Model): # partner.npwp = self.npwp # partner.sppkp = self.sppkp # partner.email = self.email - + def _compute_total_margin(self): for order in self: total_margin = sum(line.item_margin for line in order.order_line if line.product_id) + #hitung nek onk + if order.ongkir_ke_xpdc: + total_margin -= order.ongkir_ke_xpdc + order.total_margin = total_margin + # def _compute_total_margin(self): + # for order in self: + # total_margin = sum(line.item_margin for line in order.order_line if line.product_id) + # order.total_margin = total_margin + def _compute_total_percent_margin(self): for order in self: if order.amount_untaxed == 0: -- cgit v1.2.3 From 71e47cc88e250600c2975a114dd420f27fda36b3 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Tue, 22 Apr 2025 12:45:44 +0700 Subject: (andri) change min dev amt from 5000 to 100 --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 92581678..37f767ec 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -704,7 +704,7 @@ class SaleOrder(models.Model): else: raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.') - if self.delivery_amt < 5000: + if self.delivery_amt < 100: if self.carrier_id.id == 1: raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.') else: -- cgit v1.2.3 From 9e867f016484f8695fce9e0c313d41010434390c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 22 Apr 2025 15:01:28 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 96aac7a1..cd038f44 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -26,11 +26,11 @@ _biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1l class StockPicking(models.Model): _inherit = 'stock.picking' _order = 'final_seq ASC' - konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True) - scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True) - check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True) + konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True, copy=False) + scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True, copy=False) + check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True, copy=False) - check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) + check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, copy=False) barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') account_id = fields.Many2one('account.account', string='Account') @@ -100,7 +100,7 @@ class StockPicking(models.Model): ('pengajuan1', 'Approval Finance'), ('approved', 'Approved'), ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") - date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True) + date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, copy=False) note_logistic = fields.Selection([ ('hold', 'Hold by Sales'), ('not_paid', 'Customer belum bayar'), @@ -154,8 +154,8 @@ class StockPicking(models.Model): # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" quantity_koli = fields.Float(string="Quantity Koli", copy=False) total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli') - so_lama = fields.Boolean('SO LAMA') - linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out') + so_lama = fields.Boolean('SO LAMA', copy=False) + linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) # def write(self, vals): # if 'linked_manual_bu_out' in vals: -- cgit v1.2.3 From 30382037882e15bb43f56fc0d81500faeb364fa5 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 08:59:23 +0700 Subject: (andri) add field min amt tax vendor PO --- indoteknik_custom/models/purchase_order.py | 76 ++++++++++++++++++++++++++++++ indoteknik_custom/models/res_partner.py | 4 ++ 2 files changed, 80 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 5e9e509f..60d26105 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -122,6 +122,80 @@ class PurchaseOrder(models.Model): title="Payment Term Diperbarui" ) + def _check_tax_rule(self): + _logger.info("Check Tax Rule Terpanggil") + + # Pajak 11% + tax_11 = self.env['account.tax'].search([ + ('type_tax_use', '=', 'purchase'), + ('name', 'ilike', '11%') + ], limit=1) + + # Pajak "No Tax" + no_tax = self.env['account.tax'].search([ + ('type_tax_use', '=', 'purchase'), + ('name', 'ilike', 'no tax') + ], limit=1) + + if not tax_11: + raise UserError("Pajak 11% tidak ditemukan. Mohon pastikan pajak 11% tersedia.") + + if not no_tax: + raise UserError("Pajak 'No Tax' tidak ditemukan. Harap buat tax dengan nama 'No Tax' dan tipe 'Purchase'.") + + for order in self: + partner = order.partner_id + minimum_tax = partner.minimum_amount_tax + + _logger.info("Partner ID: %s, Minimum Tax: %s, Untaxed Total: %s", partner.id, minimum_tax, order.amount_untaxed) + + if not minimum_tax or not order.order_line: + continue + + if order.amount_untaxed < minimum_tax: + _logger.info(">>> Total di bawah minimum → apply No Tax") + for line in order.order_line: + line.taxes_id = [(6, 0, [no_tax.id])] + + if self.env.context.get('notify_tax'): + self.env.user.notify_info( + message="Total belanja PO belum mencapai minimum pajak vendor. " + "Pajak diganti menjadi 'No Tax'.", + title="Pajak Diperbarui", + sticky=True + ) + else: + _logger.info(">>> Total memenuhi minimum → apply Pajak 11%") + for line in order.order_line: + line.taxes_id = [(6, 0, [tax_11.id])] + + if self.env.context.get('notify_tax'): + self.env.user.notify_info( + message="Total belanja sebelum pajak telah memenuhi minimum. " + "Pajak 11%% diterapkan", + title="Pajak Diperbarui", + sticky=True + ) + + @api.onchange('order_line') + def _onchange_order_line_tax_default(self): + _logger.info("Onchange Order Line Tax Default Terpanggil") + + no_tax = self.env['account.tax'].search([ + ('type_tax_use', '=', 'purchase'), + ('name', 'ilike', 'no tax') + ], limit=1) + + if not no_tax: + _logger.info("No Tax tidak ditemukan") + return + + for order in self: + for line in order.order_line: + if not line.taxes_id: + line.taxes_id = [(6, 0, [no_tax.id])] + _logger.info("Auto-set No tax ke baris product: %s", line.product_id.name) + @api.onchange('total_cost_service') def _onchange_total_cost_service(self): for order in self: @@ -1117,12 +1191,14 @@ class PurchaseOrder(models.Model): def create(self, vals): order = super().create(vals) order.with_context(skip_check_payment=True)._check_payment_term() + order.with_context(notify_tax=True)._check_tax_rule() return order def write(self, vals): res = super().write(vals) if not self.env.context.get('skip_check_payment'): self.with_context(skip_check_payment=True)._check_payment_term() + self.with_context(notify_tax=True)._check_tax_rule() return res class PurchaseOrderUnlockWizard(models.TransientModel): diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 84edafea..191a44c9 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -44,6 +44,10 @@ class ResPartner(models.Model): string="Minimum Order", help="Jika total belanja kurang dari ini, maka payment term akan otomatis menjadi CBD." ) + minimum_amount_tax = fields.Float( + string="Minimum Amount Tax", + help="Jika total belanja kurang dari ini, maka tax akan otomatis menjadi 0%." + ) category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan', domain=lambda self: self._get_default_category_domain()) @api.model -- cgit v1.2.3 From 8334445b2946fc7383acb682ec5cde3598b8accc Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 09:01:21 +0700 Subject: (miqdad) fix duplicate voucher --- indoteknik_custom/models/voucher.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index 101d4bcf..7b458d01 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -265,3 +265,19 @@ class Voucher(models.Model): tnc.append(f'
  • {line_tnc}
  • ') return ' '.join(tnc) + # copy semua data kalau diduplicate + def copy(self, default=None): + default = dict(default or {}) + voucher_lines = [] + + for line in self.voucher_line: + voucher_lines.append((0, 0, { + 'manufacture_id': line.manufacture_id.id, + 'discount_amount': line.discount_amount, + 'discount_type': line.discount_type, + 'min_purchase_amount': line.min_purchase_amount, + 'max_discount_amount': line.max_discount_amount, + })) + + default['voucher_line'] = voucher_lines + return super(Voucher, self).copy(default) \ No newline at end of file -- cgit v1.2.3 From f3854677b0f74db2fbb54ad012b3a6a6fc9e11df Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 11:53:12 +0700 Subject: (andri) fix notify ganda pada PO --- indoteknik_custom/models/purchase_order.py | 39 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 20 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 60d26105..98b367d0 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -152,7 +152,7 @@ class PurchaseOrder(models.Model): if not minimum_tax or not order.order_line: continue - if order.amount_untaxed < minimum_tax: + if order.amount_total < minimum_tax: _logger.info(">>> Total di bawah minimum → apply No Tax") for line in order.order_line: line.taxes_id = [(6, 0, [no_tax.id])] @@ -162,7 +162,6 @@ class PurchaseOrder(models.Model): message="Total belanja PO belum mencapai minimum pajak vendor. " "Pajak diganti menjadi 'No Tax'.", title="Pajak Diperbarui", - sticky=True ) else: _logger.info(">>> Total memenuhi minimum → apply Pajak 11%") @@ -174,27 +173,27 @@ class PurchaseOrder(models.Model): message="Total belanja sebelum pajak telah memenuhi minimum. " "Pajak 11%% diterapkan", title="Pajak Diperbarui", - sticky=True ) - @api.onchange('order_line') - def _onchange_order_line_tax_default(self): - _logger.info("Onchange Order Line Tax Default Terpanggil") + # set default no_tax pada order line + # @api.onchange('order_line') + # def _onchange_order_line_tax_default(self): + # _logger.info("Onchange Order Line Tax Default Terpanggil") - no_tax = self.env['account.tax'].search([ - ('type_tax_use', '=', 'purchase'), - ('name', 'ilike', 'no tax') - ], limit=1) + # no_tax = self.env['account.tax'].search([ + # ('type_tax_use', '=', 'purchase'), + # ('name', 'ilike', 'no tax') + # ], limit=1) - if not no_tax: - _logger.info("No Tax tidak ditemukan") - return + # if not no_tax: + # _logger.info("No Tax tidak ditemukan") + # return - for order in self: - for line in order.order_line: - if not line.taxes_id: - line.taxes_id = [(6, 0, [no_tax.id])] - _logger.info("Auto-set No tax ke baris product: %s", line.product_id.name) + # for order in self: + # for line in order.order_line: + # if not line.taxes_id: + # line.taxes_id = [(6, 0, [no_tax.id])] + # _logger.info("Auto-set No tax ke baris product: %s", line.product_id.name) @api.onchange('total_cost_service') def _onchange_total_cost_service(self): @@ -1190,8 +1189,8 @@ class PurchaseOrder(models.Model): @api.model #override custom create & write for check payment term def create(self, vals): order = super().create(vals) - order.with_context(skip_check_payment=True)._check_payment_term() - order.with_context(notify_tax=True)._check_tax_rule() + # order.with_context(skip_check_payment=True)._check_payment_term() + # order.with_context(notify_tax=True)._check_tax_rule() return order def write(self, vals): -- cgit v1.2.3 From 1981b2d576d916374c181c8089655ab59a3f7f20 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 23 Apr 2025 14:29:06 +0700 Subject: barcode box --- indoteknik_custom/models/barcoding_product.py | 11 ++++-- indoteknik_custom/models/product_template.py | 2 ++ indoteknik_custom/models/stock_picking.py | 49 ++++++++++++++++++++------- 3 files changed, 46 insertions(+), 16 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index e1b8f41f..17057d1d 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -12,15 +12,20 @@ class BarcodingProduct(models.Model): barcoding_product_line = fields.One2many('barcoding.product.line', 'barcoding_product_id', string='Barcoding Product Lines', auto_join=True) product_id = fields.Many2one('product.product', string="Product", tracking=3) quantity = fields.Float(string="Quantity", tracking=3) - type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product')], string='Type', default='print') + type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product')], string='Type', default='print') barcode = fields.Char(string="Barcode") + qty_pcs_box = fields.Char(string="Quantity Pcs Box") @api.constrains('barcode') def _send_barcode_to_product(self): for record in self: if record.barcode and not record.product_id.barcode: - record.product_id.barcode = record.barcode - + if record.type == 'barcoding_box': + record.product_id.barcode_box = record.barcode + record.product_id.qty_pcs_box = record.qty_pcs_box + else: + record.product_id.barcode = record.barcode + @api.onchange('product_id', 'quantity') def _onchange_product_or_quantity(self): """Update barcoding_product_line based on product_id and quantity""" diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 600dd90e..e6a01a04 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -421,6 +421,8 @@ class ProductProduct(models.Model): plafon_qty = fields.Float(string='Max Plafon', compute='_get_plafon_qty_product') merchandise_ok = fields.Boolean(string='Product Promotion') qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') + qty_pcs_box = fields.Float("Pcs Box") + barcode_box = fields.Char("Barcode Box") def generate_product_sla(self): product_variant_ids = self.env.context.get('active_ids', []) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cd038f44..6168d3b2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1621,21 +1621,44 @@ class CheckProduct(models.Model): copy=False, ) product_id = fields.Many2one('product.product', string='Product') - quantity = fields.Float(string='Quantity', default=1.0) + quantity = fields.Float(string='Quantity') status = fields.Char(string='Status', compute='_compute_status') code_product = fields.Char(string='Code Product') @api.onchange('code_product') def _onchange_code_product(self): - if self.code_product: - product = self.env['product.product'].search([('default_code', '=', self.code_product)], limit=1) - if not product: - product = self.env['product.product'].search([('barcode', '=', self.code_product)], limit=1) - - if product: - self.product_id = product.id - else: - raise UserError("Product tidak ditemukan") + if not self.code_product: + return + + # Cari product berdasarkan default_code, barcode, atau barcode_box + product = self.env['product.product'].search([ + '|', + ('default_code', '=', self.code_product), + '|', + ('barcode', '=', self.code_product), + ('barcode_box', '=', self.code_product) + ], limit=1) + + if not product: + raise UserError("Product tidak ditemukan") + + # Jika scan barcode_box, set quantity sesuai qty_pcs_box + if product.barcode_box == self.code_product: + self.product_id = product.id + self.quantity = product.qty_pcs_box + self.code_product = product.default_code or product.barcode + # return { + # 'warning': { + # 'title': 'Info',8994175025871 + + # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}' + # } + # } + else: + # Jika scan biasa + self.product_id = product.id + self.code_product = product.default_code or product.barcode + self.quantity = 1 def unlink(self): # Get all affected pickings before deletion @@ -1763,7 +1786,7 @@ class CheckProduct(models.Model): # Find existing lines for the same product, excluding the current line existing_lines = record.picking_id.check_product_lines.filtered( - lambda line: line.product_id == record.product_id and line.id != record.id + lambda line: line.product_id == record.product_id ) if existing_lines: @@ -1771,9 +1794,9 @@ class CheckProduct(models.Model): first_line = existing_lines[0] # Calculate the total quantity after addition - total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity + total_quantity = sum(existing_lines.mapped('quantity')) - if total_quantity == total_qty_in_moves: + if total_quantity > total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) -- cgit v1.2.3 From 3f0a246d364a07f8c61eafeefcee7b37232a5933 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 14:57:14 +0700 Subject: (miqdad) rev add xpdc --- indoteknik_custom/models/sale_order.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 2061c686..bdb79fdf 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1438,19 +1438,6 @@ class SaleOrder(models.Model): # partner.sppkp = self.sppkp # partner.email = self.email - def _compute_total_margin(self): - for order in self: - total_margin = sum(line.item_margin for line in order.order_line if line.product_id) - #hitung nek onk - if order.ongkir_ke_xpdc: - total_margin -= order.ongkir_ke_xpdc - - order.total_margin = total_margin - - # def _compute_total_margin(self): - # for order in self: - # total_margin = sum(line.item_margin for line in order.order_line if line.product_id) - # order.total_margin = total_margin def _compute_total_percent_margin(self): for order in self: @@ -1768,4 +1755,13 @@ class SaleOrder(models.Model): self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() - return res \ No newline at end of file + return res + + def _compute_total_margin(self): + for order in self: + total_margin = sum(line.item_margin for line in order.order_line if line.product_id) + #hitung nek onk + if order.ongkir_ke_xpdc: + total_margin -= order.ongkir_ke_xpdc + + order.total_margin = total_margin \ No newline at end of file -- cgit v1.2.3 From f710d62fcb33e43a832d18b28baa4641f9fb65a8 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 15:39:46 +0700 Subject: (miqdad) Fix conflict Xpdc --- indoteknik_custom/models/sale_order.py | 76 ++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 18 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bdb79fdf..6b603146 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -326,7 +326,17 @@ class SaleOrder(models.Model): "sale_order_id": self.id, }) self.shipping_option_id = shipping_option.id - + self.message_post( + body=( + f"Estimasi pengiriman Indoteknik berhasil:
    " + f"Layanan: {shipping_option.name}
    " + f"ETD: {shipping_option.etd}
    " + f"Biaya: Rp {shipping_option.price:,}
    " + f"Provider: {shipping_option.provider}" + ), + message_type="comment", + ) + def action_estimate_shipping(self): if self.carrier_id.id in [1, 151]: self.action_indoteknik_estimate_shipping() @@ -365,6 +375,8 @@ class SaleOrder(models.Model): shipping_options.append((service, description, etd, value, courier['code'])) self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() + + _logger.info(f"Shipping options: {shipping_options}") for service, description, etd, value, provider in shipping_options: self.env["shipping.option"].create({ @@ -374,12 +386,23 @@ class SaleOrder(models.Model): "etd": etd, "sale_order_id": self.id, }) + self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id - self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
    Detail Lain:
    {'
    '.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}") + _logger.info(f"Shipping option SO ID: {self.shipping_option_id}") + + self.message_post( + body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
    Detail Lain:
    " + f"{'
    '.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", + message_type="comment" + ) + + # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
    Detail Lain:
    {'
    '.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment") + else: raise UserError("Gagal mendapatkan estimasi ongkir.") + def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' @@ -483,7 +506,7 @@ class SaleOrder(models.Model): def _compute_date_kirim(self): for rec in self: - picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')], order='date_doc_kirim desc', limit=1) + picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel'])], order='date_doc_kirim desc', limit=1) rec.date_kirim_ril = picking.date_doc_kirim rec.date_status_done = picking.date_done rec.date_driver_arrival = picking.driver_arrival_date @@ -682,12 +705,30 @@ class SaleOrder(models.Model): # @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered') def _validate_delivery_amt(self): - if self.delivery_amt < 1: - if(self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): - if(self.carrier_id.id == 1): - raise UserError('Untuk Kurir Indoteknik Delivery, Estimasi Ongkos Kirim Harus di isi') + is_indoteknik = self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik' + is_active_id = not self.env.context.get('active_id', []) + + if is_indoteknik and is_active_id: + if self.delivery_amt == 0: + if self.carrier_id.id == 1: + raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum diisi.') + else: + raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.') + + if self.delivery_amt < 100: + if self.carrier_id.id == 1: + raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.') else: - raise UserError('Untuk Shipping Covered Indoteknik, Estimasi Ongkos Kirim Harus di isi') + raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.') + + + # if self.delivery_amt < 5000: + # if (self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): + # if self.carrier_id.id == 1: + # raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi jumlah minimum.') + # else: + # raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi jumlah minimum.') + def override_allow_create_invoice(self): if not self.env.user.is_accounting: @@ -1437,7 +1478,15 @@ class SaleOrder(models.Model): # partner.npwp = self.npwp # partner.sppkp = self.sppkp # partner.email = self.email + + def _compute_total_margin(self): + for order in self: + total_margin = sum(line.item_margin for line in order.order_line if line.product_id) + #hitung nek onk + if order.ongkir_ke_xpdc: + total_margin -= order.ongkir_ke_xpdc + order.total_margin = total_margin def _compute_total_percent_margin(self): for order in self: @@ -1755,13 +1804,4 @@ class SaleOrder(models.Model): self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() - return res - - def _compute_total_margin(self): - for order in self: - total_margin = sum(line.item_margin for line in order.order_line if line.product_id) - #hitung nek onk - if order.ongkir_ke_xpdc: - total_margin -= order.ongkir_ke_xpdc - - order.total_margin = total_margin \ No newline at end of file + return res \ No newline at end of file -- cgit v1.2.3 From 5f78d57ced0957b5f05e590de67a4b966b22e85b Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 23 Apr 2025 15:46:28 +0700 Subject: (miqdad) fix conflict Xpdc --- indoteknik_custom/models/sale_order.py | 1 - 1 file changed, 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 6b603146..13646847 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1482,7 +1482,6 @@ class SaleOrder(models.Model): def _compute_total_margin(self): for order in self: total_margin = sum(line.item_margin for line in order.order_line if line.product_id) - #hitung nek onk if order.ongkir_ke_xpdc: total_margin -= order.ongkir_ke_xpdc -- cgit v1.2.3 From beb2ef24d462075dc390018afe1127db313fb404 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 10:39:19 +0700 Subject: push --- indoteknik_custom/models/barcoding_product.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index 17057d1d..353f94d5 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -19,12 +19,11 @@ class BarcodingProduct(models.Model): @api.constrains('barcode') def _send_barcode_to_product(self): for record in self: - if record.barcode and not record.product_id.barcode: - if record.type == 'barcoding_box': - record.product_id.barcode_box = record.barcode - record.product_id.qty_pcs_box = record.qty_pcs_box - else: - record.product_id.barcode = record.barcode + if record.type == 'barcoding_box': + record.product_id.barcode_box = record.barcode + record.product_id.qty_pcs_box = record.qty_pcs_box + else: + record.product_id.barcode = record.barcode @api.onchange('product_id', 'quantity') def _onchange_product_or_quantity(self): -- cgit v1.2.3 From 4706b80d3d3b1e55c198d2b4cfb93f7fa47c9732 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 13:49:35 +0700 Subject: validation duplicate barcode product and barcode box, cr date doc kirim, validation duplicate product id on so line --- indoteknik_custom/models/approval_date_doc.py | 9 ++++++--- indoteknik_custom/models/barcoding_product.py | 13 +++++++++++++ indoteknik_custom/models/sale_order.py | 10 ++++++++++ indoteknik_custom/models/stock_picking.py | 12 ++++++------ 4 files changed, 35 insertions(+), 9 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/approval_date_doc.py b/indoteknik_custom/models/approval_date_doc.py index 751bae82..1a2749d5 100644 --- a/indoteknik_custom/models/approval_date_doc.py +++ b/indoteknik_custom/models/approval_date_doc.py @@ -39,12 +39,15 @@ class ApprovalDateDoc(models.Model): if not self.env.user.is_accounting: raise UserError("Hanya Accounting Yang Bisa Approve") self.check_invoice_so_picking - self.picking_id.driver_departure_date = self.driver_departure_date - self.picking_id.date_doc_kirim = self.driver_departure_date + # Tambahkan context saat mengupdate date_doc_kirim + self.picking_id.with_context(from_button_approve=True).write({ + 'driver_departure_date': self.driver_departure_date, + 'date_doc_kirim': self.driver_departure_date + }) self.state = 'done' self.approve_date = datetime.utcnow() self.approve_by = self.env.user.id - + def button_cancel(self): self.state = 'cancel' diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index 353f94d5..e28473ff 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -16,9 +16,22 @@ class BarcodingProduct(models.Model): barcode = fields.Char(string="Barcode") qty_pcs_box = fields.Char(string="Quantity Pcs Box") + def check_duplicate_barcode(self): + barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)]) + + if barcode_product: + raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) + + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) + + if barcode_box: + raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name)) + + @api.constrains('barcode') def _send_barcode_to_product(self): for record in self: + record.check_duplicate_barcode() if record.type == 'barcoding_box': record.product_id.barcode_box = record.barcode record.product_id.qty_pcs_box = record.qty_pcs_box diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 39d6fd0b..02d61387 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1069,7 +1069,16 @@ class SaleOrder(models.Model): raise UserError("Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") else: raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD") + + def check_duplicate_product(self): + for order in self: + for line in order.order_line: + search_product = self.env['sale.order.line'].search([('product_id', '=', line.product_id.id), ('order_id', '=', order.id)]) + if len(search_product) > 1: + raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name)) + def sale_order_approve(self): + self.check_duplicate_product() self.check_product_bom() self.check_credit_limit() self.check_limit_so_to_invoice() @@ -1310,6 +1319,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: + order.check_duplicate_product() order.check_product_bom() order.check_credit_limit() order.check_limit_so_to_invoice() diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6168d3b2..f812df86 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -102,10 +102,9 @@ class StockPicking(models.Model): ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, copy=False) note_logistic = fields.Selection([ - ('hold', 'Hold by Sales'), + ('wait_so_together', 'Tunggu SO Barengan'), ('not_paid', 'Customer belum bayar'), - ('partial', 'Kirim Parsial'), - ('indent', 'Indent'), + ('reserve_stock', 'Reserve Stock'), ('waiting_schedule', 'Menunggu Jadwal Kirim'), ('self_pickup', 'Barang belum di pickup Customer'), ('expedition_closed', 'Eskpedisi belum buka') @@ -141,7 +140,8 @@ class StockPicking(models.Model): ('done', 'Done'), ('cancel', 'Cancelled'), ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") - notee = fields.Text(string="Note") + notee = fields.Text(string="Note SJ", help="Catatan untuk kirim barang") + note_info = fields.Text(string="Note", help="Catatan untuk pengiriman") state_approve_md = fields.Selection([ ('waiting', 'Waiting For Approve by MD'), ('pending', 'Pending (perlu koordinasi dengan MD)'), @@ -253,7 +253,7 @@ class StockPicking(models.Model): def _check_date_doc_kirim_modification(self): for record in self: - if record.last_update_date_doc_kirim: + if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'): kirim_date = fields.Datetime.from_string(record.last_update_date_doc_kirim) now = fields.Datetime.now() @@ -275,7 +275,7 @@ class StockPicking(models.Model): if invoice and not self.env.context.get('active_model') == 'stock.picking': rec._check_date_doc_kirim_modification() - if rec.date_doc_kirim != invoice.invoice_date: + if rec.date_doc_kirim != invoice.invoice_date and not self.env.context.get('from_button_approve'): get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': -- cgit v1.2.3 From add5cafc05aec0036fa1382c90dbf3abc8e1ecf5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 13:52:08 +0700 Subject: add validation barcode product --- indoteknik_custom/models/stock_picking.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f812df86..a8a4a4e6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1826,9 +1826,21 @@ class BarcodeProduct(models.Model): product_id = fields.Many2one('product.product', string='Product', required=True) barcode = fields.Char(string='Barcode') + def check_duplicate_barcode(self): + barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)]) + + if barcode_product: + raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) + + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) + + if barcode_box: + raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name)) + @api.constrains('barcode') def send_barcode_to_product(self): for record in self: + record.check_duplicate_barcode() if record.barcode and not record.product_id.barcode: record.product_id.barcode = record.barcode else: -- cgit v1.2.3 From 21653f522154475d9029c1e2b395960b58ecf47a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 13:56:11 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a8a4a4e6..8f8ab54d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -141,7 +141,7 @@ class StockPicking(models.Model): ('cancel', 'Cancelled'), ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note SJ", help="Catatan untuk kirim barang") - note_info = fields.Text(string="Note", help="Catatan untuk pengiriman") + note_info = fields.Text(string="Note Logistix (Text)", help="Catatan untuk pengiriman") state_approve_md = fields.Selection([ ('waiting', 'Waiting For Approve by MD'), ('pending', 'Pending (perlu koordinasi dengan MD)'), -- cgit v1.2.3 From 2db8d058d5b7c291669240df90afc0312d509939 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 14:29:33 +0700 Subject: fix bug --- indoteknik_custom/models/barcoding_product.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index e28473ff..204c6128 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -17,16 +17,16 @@ class BarcodingProduct(models.Model): qty_pcs_box = fields.Char(string="Quantity Pcs Box") def check_duplicate_barcode(self): - barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)]) + if self.type in ['barcoding_box', 'barcoding']: + barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)]) - if barcode_product: - raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) - - barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) - - if barcode_box: - raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name)) + if barcode_product: + raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) + + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) + if barcode_box: + raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name)) @api.constrains('barcode') def _send_barcode_to_product(self): -- cgit v1.2.3 From d6516bce4ac05a25baf060d5341f7b603961496f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 25 Apr 2025 10:39:05 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 8f8ab54d..e0ae8258 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1191,7 +1191,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.date_doc_kirim or not invoice.invoice_date: + if not picking.date_doc_kirim or not invoice.invoice_date and not picking.so_lama: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From 6dfa0b1ec5c9fd60f2adee26b7dbb1de3aea5302 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 25 Apr 2025 16:11:46 +0700 Subject: push --- indoteknik_custom/models/approval_date_doc.py | 3 ++- indoteknik_custom/models/stock_picking.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/approval_date_doc.py b/indoteknik_custom/models/approval_date_doc.py index 1a2749d5..638b44d7 100644 --- a/indoteknik_custom/models/approval_date_doc.py +++ b/indoteknik_custom/models/approval_date_doc.py @@ -42,7 +42,8 @@ class ApprovalDateDoc(models.Model): # Tambahkan context saat mengupdate date_doc_kirim self.picking_id.with_context(from_button_approve=True).write({ 'driver_departure_date': self.driver_departure_date, - 'date_doc_kirim': self.driver_departure_date + 'date_doc_kirim': self.driver_departure_date, + 'update_date_doc_kirim_add': True }) self.state = 'done' self.approve_date = datetime.utcnow() diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index e0ae8258..64dc1499 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -250,6 +250,7 @@ class StockPicking(models.Model): state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') + update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') def _check_date_doc_kirim_modification(self): for record in self: @@ -1197,7 +1198,7 @@ class StockPicking(models.Model): picking_date = fields.Date.to_date(picking.date_doc_kirim) invoice_date = fields.Date.to_date(invoice.invoice_date) - if picking_date != invoice_date: + if picking_date != invoice_date and picking.update_date_doc_kirim_add: raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % ( picking_date.strftime('%d-%m-%Y'), invoice_date.strftime('%d-%m-%Y') -- cgit v1.2.3 From 6dcc5e48adb87d43101eaa2e868d12356da092be Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 26 Apr 2025 09:50:42 +0700 Subject: Removed product from order line moved to reject line --- indoteknik_custom/models/sale_order.py | 49 ++++++++++++++++++++++++++ indoteknik_custom/models/sales_order_reject.py | 27 ++++++++++++++ 2 files changed, 76 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 02d61387..d2c49bf0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -64,6 +64,55 @@ class ShippingOption(models.Model): etd = fields.Char(string="Estimated Delivery Time") sale_order_id = fields.Many2one('sale.order', string="Sale Order", ondelete="cascade") +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + def unlink(self): + lines_to_reject = [] + for line in self: + if line.order_id: + now = fields.Datetime.now() + + initial_reason="Product Rejected" + + # Buat lognote untuk product yang di delete + log_note = (f"
  • Product '{line.product_id.name}' rejected.
  • " + f"
  • Quantity: {line.product_uom_qty},
  • " + f"
  • Date: {now.strftime('%d-%m-%Y')},
  • " + f"
  • Time: {now.strftime('%H:%M:%S')}
  • " + f"
  • Reason reject: {initial_reason}
  • ") + + lines_to_reject.append({ + 'sale_order_id': line.order_id.id, + 'product_id': line.product_id.id, + 'qty_reject': line.product_uom_qty, + 'reason_reject': initial_reason, # pesan reason reject + 'message_body': log_note, + 'order_id': line.order_id, + }) + + # Call the original unlink method + result = super(SaleOrderLine, self).unlink() + + # After deletion, create reject lines and post messages + SalesOrderReject = self.env['sales.order.reject'] + for reject_data in lines_to_reject: + # Buat line baru di reject line + SalesOrderReject.create({ + 'sale_order_id': reject_data['sale_order_id'], + 'product_id': reject_data['product_id'], + 'qty_reject': reject_data['qty_reject'], + 'reason_reject': reject_data['reason_reject'], + }) + + # Post to chatter with a more prominent message + reject_data['order_id'].message_post( + body=reject_data['message_body'], + author_id=self.env.user.partner_id.id, # menampilkan pesan di lognote sebagai current user + ) + + return result + class SaleOrder(models.Model): _inherit = "sale.order" diff --git a/indoteknik_custom/models/sales_order_reject.py b/indoteknik_custom/models/sales_order_reject.py index 9983c64e..b180fad6 100644 --- a/indoteknik_custom/models/sales_order_reject.py +++ b/indoteknik_custom/models/sales_order_reject.py @@ -13,3 +13,30 @@ class SalesOrderReject(models.Model): product_id = fields.Many2one('product.product', string='Product') qty_reject = fields.Float(string='Qty') reason_reject = fields.Char(string='Reason Reject') + + def write(self, vals): + # Check if reason_reject is being updated + if 'reason_reject' in vals: + for record in self: + old_reason = record.reason_reject + new_reason = vals['reason_reject'] + + # Only post a message if the reason actually changed + if old_reason != new_reason: + now = fields.Datetime.now() + + # Create the log note for the updated reason + log_note = (f"
  • Product '{record.product_id.name}' rejection reason updated:
  • " + f"
  • From: {old_reason}
  • " + f"
  • To: {new_reason}
  • " + f"
  • Updated on: {now.strftime('%d-%m-%Y')} at {now.strftime('%H:%M:%S')}
  • ") + + # Post ke lognote + if record.sale_order_id: + record.sale_order_id.message_post( + body=log_note, + author_id=self.env.user.partner_id.id, + ) + + # Call the original write method + return super(SalesOrderReject, self).write(vals) \ No newline at end of file -- cgit v1.2.3 From 8b532b193a47c6be35bcfb49b77849458a379cee Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 28 Apr 2025 09:34:12 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 64dc1499..a6b3373f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1192,7 +1192,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.date_doc_kirim or not invoice.invoice_date and not picking.so_lama: + if picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From bac1744ce4e27d796fd2b52f5fbcd3d5cdabdc75 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 28 Apr 2025 09:35:23 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a6b3373f..d2b1b9f2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1192,7 +1192,7 @@ class StockPicking(models.Model): if not invoice: continue - if picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: + if not picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From a2a05e1183f9829b09d2e065815cb3618cc05049 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 28 Apr 2025 09:43:02 +0700 Subject: (andri) rev fix margin approval untuk 1 Mei --- indoteknik_custom/models/sale_order.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 02d61387..ab63509b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1451,11 +1451,13 @@ class SaleOrder(models.Model): due_extension.unlink() return False + # Rule: # ≤ 15% → Pimpinan, > 15% - <24% → Manager, == 24% → Langsung Confirm def _requires_approval_margin_leader(self): - return self.total_percent_margin < 15 and not self.env.user.is_leader + return self.total_percent_margin <= 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager + return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader + # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 082a4cf4c1f109644cb5c7a45ed1d3ffa69b1b06 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 28 Apr 2025 14:42:51 +0700 Subject: (andri) Add validation: BOM cannot be mark done if SO has not been confirmed --- indoteknik_custom/models/manufacturing.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/manufacturing.py b/indoteknik_custom/models/manufacturing.py index 24a8b8c3..715d8513 100644 --- a/indoteknik_custom/models/manufacturing.py +++ b/indoteknik_custom/models/manufacturing.py @@ -26,6 +26,13 @@ class Manufacturing(models.Model): # Check product category if self.product_id.categ_id.name != 'Finish Good': raise UserError('Tidak bisa di complete karna product category bukan Unit / Finish Good') + + if self.sale_order and self.sale_order.state != 'sale': + raise UserError( + ('Tidak bisa Mark as Done.\nSales Order "%s" (Nomor: %s) belum dikonfirmasi.') + % (self.sale_order.partner_id.name, self.sale_order.name) + ) + for line in self.move_raw_ids: # if line.quantity_done > 0 and line.quantity_done != self.product_uom_qty: # raise UserError('Qty Consume per Line tidak sama dengan Qty to Produce') -- cgit v1.2.3 From f2579dfcf01ab1687deb7221b50129fadb95d671 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 28 Apr 2025 15:03:38 +0700 Subject: (andri) undo fix margin --- indoteknik_custom/models/sale_order.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index ab63509b..02d61387 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1451,13 +1451,11 @@ class SaleOrder(models.Model): due_extension.unlink() return False - # Rule: # ≤ 15% → Pimpinan, > 15% - <24% → Manager, == 24% → Langsung Confirm def _requires_approval_margin_leader(self): - return self.total_percent_margin <= 15 and not self.env.user.is_leader + return self.total_percent_margin < 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader - # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager + return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 783b674e04dd123a5233fd01896925c73aa8143c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 29 Apr 2025 10:00:15 +0700 Subject: check product on bom, view stock picking po and fix bug api flashsale header --- indoteknik_custom/models/mrp_production.py | 284 +++++++++++++++++++++++++++++ indoteknik_custom/models/stock_move.py | 15 ++ 2 files changed, 299 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index d80df2ce..ebbd1c24 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -8,11 +8,34 @@ from odoo.exceptions import AccessError, UserError, ValidationError class MrpProduction(models.Model): _inherit = 'mrp.production' + check_bom_product_lines = fields.One2many('check.bom.product', 'production_id', string='Check Product', auto_join=True, copy=False) desc = fields.Text(string='Description') sale_order = fields.Many2one('sale.order', string='Sale Order', required=True, copy=False) production_purchase_match = fields.One2many('production.purchase.match', 'production_id', string='Purchase Matches', auto_join=True) is_po = fields.Boolean(string='Is PO') + @api.constrains('check_bom_product_lines') + def constrains_check_bom_product_lines(self): + for rec in self: + if len(rec.check_bom_product_lines) > 0: + rec.qty_producing = rec.product_qty + + def button_mark_done(self): + """Override button_mark_done untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'.""" + if self._name != 'mrp.production': + return super(MrpProduction, self).button_mark_done() + + result = super(MrpProduction, self).button_mark_done() + + for record in self: + if len(record.check_bom_product_lines) < 1: + raise UserError("Check Product Tidak Boleh Kosong") + if record.sale_order and record.state == 'confirmed': + message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name) + record.sale_order.message_post(body=message) + + return result + def action_confirm(self): """Override action_confirm untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'.""" if self._name != 'mrp.production': @@ -21,6 +44,8 @@ class MrpProduction(models.Model): result = super(MrpProduction, self).action_confirm() for record in self: + # if len(record.check_bom_product_lines) < 1: + # raise UserError("Check Product Tidak Boleh Kosong") if record.sale_order and record.state == 'confirmed': message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name) record.sale_order.message_post(body=message) @@ -171,6 +196,265 @@ class MrpProduction(models.Model): return price, taxes, vendor_id +class CheckBomProduct(models.Model): + _name = 'check.bom.product' + _description = 'Check Product' + _order = 'production_id, id' + + production_id = fields.Many2one( + 'mrp.production', + string='Bom Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + product_id = fields.Many2one('product.product', string='Product') + quantity = fields.Float(string='Quantity') + status = fields.Char(string='Status', compute='_compute_status') + code_product = fields.Char(string='Code Product') + + @api.constrains('production_id') + def _check_missing_components(self): + for mo in self: + required = mo.production_id.move_raw_ids.mapped('product_id') + entered = mo.production_id.check_bom_product_lines.mapped('product_id') + missing = required - entered + + # Jika HTML tidak bekerja sama sekali, gunakan format text biasa yang rapi + if missing: + product_list = "\n- " + "\n- ".join(p.display_name for p in missing) + raise UserError( + "⚠️ Komponen Wajib Diisi\n\n" + "Produk berikut harus ditambahkan:\n" + f"{product_list}\n\n" + "Silakan lengkapi terlebih dahulu." + ) + + @api.constrains('production_id', 'product_id') + def _check_product_bom_validation(self): + for record in self: + if not record.production_id or not record.product_id: + continue + + moves = record.production_id.move_raw_ids.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + + if not moves: + raise UserError(( + "The product '%s' tidak ada di operations. " + ) % record.product_id.display_name) + + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + # Find existing lines for the same product, excluding the current line + existing_lines = record.production_id.check_bom_product_lines.filtered( + lambda line: line.product_id == record.product_id + ) + + if existing_lines: + total_quantity = sum(existing_lines.mapped('quantity')) + + if total_quantity < total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' kurang dari quantity demand." + ) % (record.product_id.display_name)) + else: + # Check if the quantity exceeds the allowed total + if record.quantity < total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' kurang dari quantity demand." + ) % (record.product_id.display_name)) + + # Set the quantity to the entered value + record.quantity = record.quantity + + @api.onchange('code_product') + def _onchange_code_product(self): + if not self.code_product: + return + + # Cari product berdasarkan default_code, barcode, atau barcode_box + product = self.env['product.product'].search([ + '|', + ('default_code', '=', self.code_product), + '|', + ('barcode', '=', self.code_product), + ('barcode_box', '=', self.code_product) + ], limit=1) + + if not product: + raise UserError("Product tidak ditemukan") + + # Jika scan barcode_box, set quantity sesuai qty_pcs_box + if product.barcode_box == self.code_product: + self.product_id = product.id + self.quantity = product.qty_pcs_box + self.code_product = product.default_code or product.barcode + # return { + # 'warning': { + # 'title': 'Info',8994175025871 + + # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}' + # } + # } + else: + # Jika scan biasa + self.product_id = product.id + self.code_product = product.default_code or product.barcode + self.quantity = 1 + + def unlink(self): + # Get all affected pickings before deletion + productions = self.mapped('production_id') + + # Store product_ids that will be deleted + deleted_product_ids = self.mapped('product_id') + + # Perform the deletion + result = super(CheckBomProduct, self).unlink() + + # After deletion, update moves for affected pickings + for production in productions: + # For products that were completely removed (no remaining check.bom.product lines) + remaining_product_ids = production.check_bom_product_lines.mapped('product_id') + removed_product_ids = deleted_product_ids - remaining_product_ids + + # Set quantity_done to 0 for moves of completely removed products + moves_to_reset = production.move_raw_ids.filtered( + lambda move: move.product_id in removed_product_ids + ) + for move in moves_to_reset: + move.quantity_done = 0.0 + + # Also sync remaining products in case their totals changed + self._sync_check_product_to_moves(production) + + return result + + @api.depends('quantity') + def _compute_status(self): + for record in self: + moves = record.production_id.move_raw_ids.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + if record.quantity < total_qty_in_moves: + record.status = 'Pending' + else: + record.status = 'Done' + + + def create(self, vals): + # Create the record + record = super(CheckBomProduct, self).create(vals) + # Ensure uniqueness after creation + if not self.env.context.get('skip_consolidate'): + record.with_context(skip_consolidate=True)._consolidate_duplicate_lines() + return record + + def write(self, vals): + # Write changes to the record + result = super(CheckBomProduct, self).write(vals) + # Ensure uniqueness after writing + if not self.env.context.get('skip_consolidate'): + self.with_context(skip_consolidate=True)._consolidate_duplicate_lines() + return result + + def _sync_check_product_to_moves(self, production): + """ + Sinkronisasi quantity_done di move_raw_ids + dengan total quantity dari check.bom.product berdasarkan product_id. + """ + for product_id in production.check_bom_product_lines.mapped('product_id'): + # Totalkan quantity dari semua baris check.bom.product untuk product_id ini + total_quantity = sum( + line.quantity for line in production.check_bom_product_lines.filtered(lambda line: line.product_id == product_id) + ) + # Update quantity_done di move yang relevan + moves = production.move_raw_ids.filtered(lambda move: move.product_id == product_id) + for move in moves: + move.quantity_done = total_quantity + + def _consolidate_duplicate_lines(self): + """ + Consolidate duplicate lines with the same product_id under the same production_id + and sync the total quantity to related moves. + """ + for production in self.mapped('production_id'): + lines_to_remove = self.env['check.bom.product'] # Recordset untuk menyimpan baris yang akan dihapus + product_lines = production.check_bom_product_lines.filtered(lambda line: line.product_id) + + # Group lines by product_id + product_groups = {} + for line in product_lines: + product_groups.setdefault(line.product_id.id, []).append(line) + + for product_id, lines in product_groups.items(): + if len(lines) > 1: + # Consolidate duplicate lines + first_line = lines[0] + total_quantity = sum(line.quantity for line in lines) + + # Update the first line's quantity + first_line.with_context(skip_consolidate=True).write({'quantity': total_quantity}) + + # Add the remaining lines to the lines_to_remove recordset + lines_to_remove |= self.env['check.bom.product'].browse([line.id for line in lines[1:]]) + + # Perform unlink after consolidation + if lines_to_remove: + lines_to_remove.unlink() + + # Sync total quantities to moves + self._sync_check_product_to_moves(production) + + @api.onchange('product_id', 'quantity') + def check_product_validity(self): + for record in self: + if not record.production_id or not record.product_id: + continue + + # Filter moves related to the selected product + moves = record.production_id.move_raw_ids.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + + if not moves: + raise UserError(( + "The product '%s' tidak ada di operations. " + ) % record.product_id.display_name) + + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + # Find existing lines for the same product, excluding the current line + existing_lines = record.production_id.check_bom_product_lines.filtered( + lambda line: line.product_id == record.product_id + ) + + if existing_lines: + # Get the first existing line + first_line = existing_lines[0] + + # Calculate the total quantity after addition + total_quantity = sum(existing_lines.mapped('quantity')) + + if total_quantity > total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) + else: + # Check if the quantity exceeds the allowed total + if record.quantity == total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) + + # Set the quantity to the entered value + record.quantity = record.quantity + class ProductionPurchaseMatch(models.Model): _name = 'production.purchase.match' diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 514acad0..e75c75f0 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -15,6 +15,21 @@ class StockMove(models.Model): barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') + @api.model_create_multi + def create(self, vals_list): + moves = super(StockMove, self).create(vals_list) + + for move in moves: + if move.product_id and move.location_id.id == 58 and move.location_dest_id.id == 57 and move.picking_type_id.id == 75: + po_line = self.env['purchase.order.line'].search([ + ('product_id', '=', move.product_id.id), + ('order_id.name', '=', move.origin) + ], limit=1) + if po_line: + move.write({'purchase_line_id': po_line.id}) + + return moves + @api.constrains('product_id') def constrains_product_to_fill_vendor(self): for rec in self: -- cgit v1.2.3 From 9642f8efdd9b7a63b58788251959e5968db8d7ee Mon Sep 17 00:00:00 2001 From: AndriFP Date: Wed, 30 Apr 2025 08:18:07 +0700 Subject: (andri) delete comment code --- indoteknik_custom/models/sale_order.py | 1 - 1 file changed, 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index ab63509b..b0643874 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1457,7 +1457,6 @@ class SaleOrder(models.Model): def _requires_approval_margin_manager(self): return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader - # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 53d840b330769988ac9efd4c4c6fe4f21969d850 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Wed, 30 Apr 2025 08:24:04 +0700 Subject: (andri) Penyesuaian Margin Approval (Live 1 Mei) --- indoteknik_custom/models/sale_order.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 37f767ec..78f2fa38 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1431,10 +1431,11 @@ class SaleOrder(models.Model): return False def _requires_approval_margin_leader(self): - return self.total_percent_margin < 15 and not self.env.user.is_leader + return self.total_percent_margin <= 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager + return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader + # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 475173ec46981926aa90e99f796e38ef1d46b61e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 10:15:35 +0700 Subject: sequence pricelist and fix bug validation invoice date and date doc kirim --- indoteknik_custom/models/product_pricelist.py | 1 + indoteknik_custom/models/stock_picking.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_pricelist.py b/indoteknik_custom/models/product_pricelist.py index c299ff2f..ea3ee6cf 100644 --- a/indoteknik_custom/models/product_pricelist.py +++ b/indoteknik_custom/models/product_pricelist.py @@ -17,6 +17,7 @@ class ProductPricelist(models.Model): ], string='Flash Sale Option') banner_top = fields.Binary(string='Banner Top') flashsale_tag = fields.Char(string='Flash Sale Tag') + number = fields.Integer(string='Sequence') def _check_end_date_and_update_solr(self): today = datetime.utcnow().date() diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d2b1b9f2..0b688fab 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1187,7 +1187,7 @@ class StockPicking(models.Model): if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name or picking.partner_id.id == 96868: continue - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)], limit=1) + invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id), ('state','not in',['draft','cancel'])], limit=1) if not invoice: continue -- cgit v1.2.3 From 7b5b79e03e08dff76981dd9734d20c52f90c0b36 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 11:49:18 +0700 Subject: cr mrp production sale order required --- indoteknik_custom/models/mrp_production.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index ebbd1c24..87d75faf 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -10,7 +10,7 @@ class MrpProduction(models.Model): check_bom_product_lines = fields.One2many('check.bom.product', 'production_id', string='Check Product', auto_join=True, copy=False) desc = fields.Text(string='Description') - sale_order = fields.Many2one('sale.order', string='Sale Order', required=True, copy=False) + sale_order = fields.Many2one('sale.order', string='Sale Order', copy=False) production_purchase_match = fields.One2many('production.purchase.match', 'production_id', string='Purchase Matches', auto_join=True) is_po = fields.Boolean(string='Is PO') @@ -30,6 +30,8 @@ class MrpProduction(models.Model): for record in self: if len(record.check_bom_product_lines) < 1: raise UserError("Check Product Tidak Boleh Kosong") + if not record.sale_order: + raise UserError("Sale Order Tidak Boleh Kosong") if record.sale_order and record.state == 'confirmed': message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name) record.sale_order.message_post(body=message) @@ -327,6 +329,8 @@ class CheckBomProduct(models.Model): ) for move in moves_to_reset: move.quantity_done = 0.0 + + production.qty_producing = 0 # Also sync remaining products in case their totals changed self._sync_check_product_to_moves(production) -- cgit v1.2.3 From 6f93b7a7a47481f6f308295a49bce40505041411 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 14:55:49 +0700 Subject: (andri) add field area (kecamatan & kota) pada stock picking --- indoteknik_custom/models/stock_picking.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0b688fab..ccb551b0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,6 +157,15 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) + area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) + + @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + def _compute_area_name(self): + for record in self: + district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' + city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + record.area_name = f"{district}, {city}".strip(', ') + # def write(self, vals): # if 'linked_manual_bu_out' in vals: # for record in self: -- cgit v1.2.3 From a73856faaa1999b0c5ec20c41b37fda8a8eed31a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 15:13:52 +0700 Subject: (andri) add area (kecamatan kota) pada stock picking --- indoteknik_custom/models/stock_picking.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 64dc1499..75803ccb 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,6 +157,14 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) + area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) + @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + def _compute_area_name(self): + for record in self: + district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' + city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + record.area_name = f"{district}, {city}".strip(', ') + # def write(self, vals): # if 'linked_manual_bu_out' in vals: # for record in self: -- cgit v1.2.3 From ee062e41281b7a1f582ff299ba4c671a4bbbe24d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 15:28:43 +0700 Subject: (andri) undo penyesuaian margin pada branch ini (agar tidak konflik) --- indoteknik_custom/models/sale_order.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b0643874..ab63509b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1457,6 +1457,7 @@ class SaleOrder(models.Model): def _requires_approval_margin_manager(self): return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader + # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 6601e72946ebcbca6b73b20dd4f3f86f39f89265 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 15:29:04 +0700 Subject: (andri) undo penyesuaian margin pada branch ini (agar tidak konflik) --- indoteknik_custom/models/sale_order.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index ab63509b..e25b9570 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1453,11 +1453,10 @@ class SaleOrder(models.Model): # Rule: # ≤ 15% → Pimpinan, > 15% - <24% → Manager, == 24% → Langsung Confirm def _requires_approval_margin_leader(self): - return self.total_percent_margin <= 15 and not self.env.user.is_leader + return self.total_percent_margin < 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader - # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager + return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): title = 'Warning' -- cgit v1.2.3 From 4c4414b0a4b0a51acfe7324c4c556cd0aa57c3c6 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 15:35:02 +0700 Subject: push --- indoteknik_custom/models/mrp_production.py | 8 ++++++++ indoteknik_custom/models/stock_move.py | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 87d75faf..8179fe56 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -13,6 +13,14 @@ class MrpProduction(models.Model): sale_order = fields.Many2one('sale.order', string='Sale Order', copy=False) production_purchase_match = fields.One2many('production.purchase.match', 'production_id', string='Purchase Matches', auto_join=True) is_po = fields.Boolean(string='Is PO') + state_reserve = fields.Selection([ + ('waiting', 'Waiting For Fullfilment'), + ('ready', 'Ready to Ship'), + ('done', 'Done'), + ('cancel', 'Cancelled'), + ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") + date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False) + @api.constrains('check_bom_product_lines') def constrains_check_bom_product_lines(self): diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index e75c75f0..42a6307a 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -15,18 +15,18 @@ class StockMove(models.Model): barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') - @api.model_create_multi - def create(self, vals_list): - moves = super(StockMove, self).create(vals_list) + # @api.model_create_multi + # def create(self, vals_list): + # moves = super(StockMove, self).create(vals_list) - for move in moves: - if move.product_id and move.location_id.id == 58 and move.location_dest_id.id == 57 and move.picking_type_id.id == 75: - po_line = self.env['purchase.order.line'].search([ - ('product_id', '=', move.product_id.id), - ('order_id.name', '=', move.origin) - ], limit=1) - if po_line: - move.write({'purchase_line_id': po_line.id}) + # for move in moves: + # if move.product_id and move.location_id.id == 58 and move.location_dest_id.id == 57 and move.picking_type_id.id == 75: + # po_line = self.env['purchase.order.line'].search([ + # ('product_id', '=', move.product_id.id), + # ('order_id.name', '=', move.origin) + # ], limit=1) + # if po_line: + # move.write({'purchase_line_id': po_line.id}) return moves -- cgit v1.2.3 From f7dbba32f167b988cdbfbc0d89664e5595460a9c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 15:37:46 +0700 Subject: fix bug --- indoteknik_custom/models/stock_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 42a6307a..3c765244 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -28,7 +28,7 @@ class StockMove(models.Model): # if po_line: # move.write({'purchase_line_id': po_line.id}) - return moves + # return moves @api.constrains('product_id') def constrains_product_to_fill_vendor(self): -- cgit v1.2.3 From de1f159b50d42942381c0c7ab28e600b091c22ca Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 17:22:17 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f71c07e0..c35c3047 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,12 +157,12 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) - area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) - @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + area_name = fields.Char(string="Area", compute="_compute_area_name") + # @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') def _compute_area_name(self): for record in self: - district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' - city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + district = record.real_shipping_id.kelurahan_id.name if record.real_shipping_id.kelurahan_id else '' + city = record.real_shipping_id.kota_id.name if record.real_shipping_id.kota_id else '' record.area_name = f"{district}, {city}".strip(', ') # def write(self, vals): -- cgit v1.2.3 From c4dbf79115f0a468be9ff6e63f5c0dd4a7cf1718 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 2 May 2025 08:55:20 +0700 Subject: (andri) add validasi tax pada SO harus seragam --- indoteknik_custom/models/sale_order.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index e25b9570..e5ec271e 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -248,6 +248,16 @@ class SaleOrder(models.Model): nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") + def _validate_uniform_taxes(self): + for order in self: + tax_sets = set() + for line in order.order_line: + tax_ids = tuple(sorted(line.tax_id.ids)) + if tax_ids: + tax_sets.add(tax_ids) + if len(tax_sets) > 1: + raise ValidationError("Semua produk dalam Sales Order harus memiliki kombinasi pajak yang sama.") + @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') def _check_total_margin_excl_third_party(self): for rec in self: @@ -1036,6 +1046,7 @@ class SaleOrder(models.Model): self._validate_order() for order in self: + order._validate_uniform_taxes() order.order_line.validate_line() term_days = 0 @@ -1088,6 +1099,7 @@ class SaleOrder(models.Model): self._validate_order() for order in self: + order._validate_uniform_taxes() order.order_line.validate_line() order.check_data_real_delivery_address() order._validate_order() @@ -1319,6 +1331,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: + order._validate_uniform_taxes() order.check_duplicate_product() order.check_product_bom() order.check_credit_limit() -- cgit v1.2.3 From de3efad6b229594019754fc10db7c33db54fdcf4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 2 May 2025 13:38:37 +0700 Subject: change default status default value --- indoteknik_custom/models/commision.py | 103 +++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 46 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 6d832b85..2f3789eb 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -13,8 +13,10 @@ class CustomerRebate(models.Model): _inherit = ['mail.thread'] partner_id = fields.Many2one('res.partner', string='Customer', required=True) - date_from = fields.Date(string='Date From', required=True, help="Pastikan tanggal 1 januari, jika tidak, code akan break") - date_to = fields.Date(string='Date To', required=True, help="Pastikan tanggal 31 desember, jika tidak, code akan break") + date_from = fields.Date(string='Date From', required=True, + help="Pastikan tanggal 1 januari, jika tidak, code akan break") + date_to = fields.Date(string='Date To', required=True, + help="Pastikan tanggal 31 desember, jika tidak, code akan break") description = fields.Char(string='Description') target_1st = fields.Float(string='Target/Quarter 1st') target_2nd = fields.Float(string='Target/Quarter 2nd') @@ -36,7 +38,7 @@ class CustomerRebate(models.Model): line.dpp_q2 = line._get_current_dpp_q2(line) line.dpp_q3 = line._get_current_dpp_q3(line) line.dpp_q4 = line._get_current_dpp_q4(line) - + def _compute_achievement(self): for line in self: line.status_q1 = line._check_achievement(line.target_1st, line.target_2nd, line.dpp_q1) @@ -53,18 +55,18 @@ class CustomerRebate(models.Model): else: status = 'not achieve' return status - + def _get_current_dpp_q1(self, line): sum_dpp = 0 brand = [10, 89, 122] where = [ - ('move_id.move_type', '=', 'out_invoice'), - ('move_id.state', '=', 'posted'), - ('move_id.is_customer_commision', '=', False), - ('move_id.partner_id.id', '=', line.partner_id.id), - ('move_id.invoice_date', '>=', line.date_from), - ('move_id.invoice_date', '<=', '2023-03-31'), - ('product_id.x_manufacture', 'in', brand), + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'posted'), + ('move_id.is_customer_commision', '=', False), + ('move_id.partner_id.id', '=', line.partner_id.id), + ('move_id.invoice_date', '>=', line.date_from), + ('move_id.invoice_date', '<=', '2023-03-31'), + ('product_id.x_manufacture', 'in', brand), ] invoice_lines = self.env['account.move.line'].search(where, order='id') for invoice_line in invoice_lines: @@ -75,13 +77,13 @@ class CustomerRebate(models.Model): sum_dpp = 0 brand = [10, 89, 122] where = [ - ('move_id.move_type', '=', 'out_invoice'), - ('move_id.state', '=', 'posted'), - ('move_id.is_customer_commision', '=', False), - ('move_id.partner_id.id', '=', line.partner_id.id), - ('move_id.invoice_date', '>=', '2023-04-01'), - ('move_id.invoice_date', '<=', '2023-06-30'), - ('product_id.x_manufacture', 'in', brand), + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'posted'), + ('move_id.is_customer_commision', '=', False), + ('move_id.partner_id.id', '=', line.partner_id.id), + ('move_id.invoice_date', '>=', '2023-04-01'), + ('move_id.invoice_date', '<=', '2023-06-30'), + ('product_id.x_manufacture', 'in', brand), ] invoices = self.env['account.move.line'].search(where, order='id') for invoice in invoices: @@ -92,13 +94,13 @@ class CustomerRebate(models.Model): sum_dpp = 0 brand = [10, 89, 122] where = [ - ('move_id.move_type', '=', 'out_invoice'), - ('move_id.state', '=', 'posted'), - ('move_id.is_customer_commision', '=', False), - ('move_id.partner_id.id', '=', line.partner_id.id), - ('move_id.invoice_date', '>=', '2023-07-01'), - ('move_id.invoice_date', '<=', '2023-09-30'), - ('product_id.x_manufacture', 'in', brand), + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'posted'), + ('move_id.is_customer_commision', '=', False), + ('move_id.partner_id.id', '=', line.partner_id.id), + ('move_id.invoice_date', '>=', '2023-07-01'), + ('move_id.invoice_date', '<=', '2023-09-30'), + ('product_id.x_manufacture', 'in', brand), ] invoices = self.env['account.move.line'].search(where, order='id') for invoice in invoices: @@ -109,19 +111,20 @@ class CustomerRebate(models.Model): sum_dpp = 0 brand = [10, 89, 122] where = [ - ('move_id.move_type', '=', 'out_invoice'), - ('move_id.state', '=', 'posted'), - ('move_id.is_customer_commision', '=', False), - ('move_id.partner_id.id', '=', line.partner_id.id), - ('move_id.invoice_date', '>=', '2023-10-01'), - ('move_id.invoice_date', '<=', line.date_to), - ('product_id.x_manufacture', 'in', brand), + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'posted'), + ('move_id.is_customer_commision', '=', False), + ('move_id.partner_id.id', '=', line.partner_id.id), + ('move_id.invoice_date', '>=', '2023-10-01'), + ('move_id.invoice_date', '<=', line.date_to), + ('product_id.x_manufacture', 'in', brand), ] invoices = self.env['account.move.line'].search(where, order='id') for invoice in invoices: sum_dpp += invoice.price_subtotal return sum_dpp + class RejectReasonCommision(models.TransientModel): _name = 'reject.reason.commision' _description = 'Wizard for Reject Reason Customer Commision' @@ -150,16 +153,19 @@ class CustomerCommision(models.Model): partner_ids = fields.Many2many('res.partner', String='Customer', required=True) description = fields.Char(string='Description') notification = fields.Char(string='Notification') - commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', auto_join=True) + commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', + auto_join=True) status = fields.Selection([ + ('draft', 'Draft'), ('pengajuan1', 'Menunggu Approval Manager Sales'), ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), ('approved', 'Approved'), ('reject', 'Rejected'), - ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange',default='draft') + ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange', default='draft') last_status = fields.Selection([ + ('draft', 'Draft'), ('pengajuan1', 'Menunggu Approval Manager Sales'), ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), @@ -209,7 +215,7 @@ class CustomerCommision(models.Model): for record in self: res = '' - + try: if record.commision_amt > 0: tb.parse(int(record.commision_amt)) @@ -222,15 +228,16 @@ class CustomerCommision(models.Model): for rec in self: so_numbers = set() invoice_numbers = set() - + for line in rec.commision_lines: - if line.invoice_id: + if line.invoice_id: if line.invoice_id.sale_id: so_numbers.add(line.invoice_id.sale_id.name) invoice_numbers.add(line.invoice_id.name) - + rec.grouped_so_number = ', '.join(sorted(so_numbers)) rec.grouped_invoice_number = ', '.join(sorted(invoice_numbers)) + # add status for type of commision, fee, rebate / cashback # include child or not? @@ -255,22 +262,22 @@ class CustomerCommision(models.Model): self.commision_percent = achieve_2nd else: self.commision_percent = 0 - + self._onchange_commision_amt() @api.constrains('commision_percent') def _onchange_commision_percent(self): if not self.env.context.get('_onchange_commision_percent', True): return - + if self.commision_amt == 0: self.commision_amt = self.commision_percent * self.total_dpp // 100 - + @api.constrains('commision_amt') def _onchange_commision_amt(self): if not self.env.context.get('_onchange_commision_amt', True): return - + if self.total_dpp > 0 and self.commision_percent == 0: self.commision_percent = (self.commision_amt / self.total_dpp) * 100 @@ -322,7 +329,7 @@ class CustomerCommision(models.Model): raise UserError('Harus di approved oleh yang bersangkutan') return - def action_reject(self):#add 2 step approval + def action_reject(self): # add 2 step approval return { 'type': 'ir.actions.act_window', 'name': _('Reject Reason'), @@ -353,7 +360,7 @@ class CustomerCommision(models.Model): def generate_customer_commision(self): if self.commision_lines: raise UserError('Line sudah ada, tidak bisa di generate ulang') - + if self.commision_type == 'fee': self._generate_customer_commision_fee() else: @@ -428,11 +435,13 @@ class CustomerCommision(models.Model): }]) return + class CustomerCommisionLine(models.Model): _name = 'customer.commision.line' _order = 'id' - customer_commision_id = fields.Many2one('customer.commision', string='Ref', required=True, ondelete='cascade', copy=False) + customer_commision_id = fields.Many2one('customer.commision', string='Ref', required=True, ondelete='cascade', + copy=False) invoice_id = fields.Many2one('account.move', string='Invoice') partner_id = fields.Many2one('res.partner', string='Customer') state = fields.Char(string='InvStatus') @@ -440,10 +449,12 @@ class CustomerCommisionLine(models.Model): tax = fields.Float(string='TaxAmt') total = fields.Float(string='Total') total_percent_margin = fields.Float('Total Margin', related='invoice_id.sale_id.total_percent_margin') - total_margin_excl_third_party = fields.Float('Before Margin', related='invoice_id.sale_id.total_margin_excl_third_party') + total_margin_excl_third_party = fields.Float('Before Margin', + related='invoice_id.sale_id.total_margin_excl_third_party') product_id = fields.Many2one('product.product', string='Product') sale_order_id = fields.Many2one('sale.order', string='Sale Order', related='invoice_id.sale_id') + class AccountMove(models.Model): _inherit = 'account.move' is_customer_commision = fields.Boolean(string='Customer Commision Used') -- cgit v1.2.3 From bb8a5981bdfd8a8d60db7fe509a989c29b2a4e5e Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 2 May 2025 15:49:43 +0700 Subject: (andri) rev area list picking --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ccb551b0..e178ad1c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,13 +157,13 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) - area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) + area_name = fields.Char(string="Area", compute="_compute_area_name") - @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: - district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' - city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + district = record.real_shipping_id.kecamatan_id or '' + city = record.real_shipping_id.kota_id or '' record.area_name = f"{district}, {city}".strip(', ') # def write(self, vals): -- cgit v1.2.3 From dcfe69ad989118ce2d8dc41a9af1ece6b5a57d76 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 16:38:48 +0700 Subject: image carousel product --- indoteknik_custom/models/product_template.py | 18 +++++++++++++++++- indoteknik_custom/models/solr/product_product.py | 3 ++- indoteknik_custom/models/solr/product_template.py | 3 ++- 3 files changed, 21 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index e6a01a04..a09570f4 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -15,6 +15,14 @@ _logger = logging.getLogger(__name__) class ProductTemplate(models.Model): _inherit = "product.template" + + image_carousel_lines = fields.One2many( + comodel_name="image.carousel", + inverse_name="product_id", + string="Image Carousel", + auto_join=True, + copy=False + ) x_studio_field_tGhJR = fields.Many2many('x_product_tags', string="Product Tags") x_manufacture = fields.Many2one( comodel_name="x_manufactures", @@ -246,7 +254,7 @@ class ProductTemplate(models.Model): # product.default_code = 'ITV.'+str(product.id) # _logger.info('Updated Variant %s' % product.name) - @api.onchange('name','default_code','x_manufacture','product_rating','website_description','image_1920','weight','public_categ_ids') + @api.onchange('name','default_code','x_manufacture','product_rating','website_description','image_1920','weight','public_categ_ids','image_carousel_lines') def update_solr_flag(self): for tmpl in self: if tmpl.solr_flag == 1: @@ -734,3 +742,11 @@ class OutstandingMove(models.Model): 'partially_available' ) """ % self._table) + +class ImageCarousel(models.Model): + _name = 'image.carousel' + _description = 'Image Carousel' + _order = 'product_id, id' + + product_id = fields.Many2one('product.template', string='Product', required=True, ondelete='cascade', index=True, copy=False) + image = fields.Binary(string='Image') diff --git a/indoteknik_custom/models/solr/product_product.py b/indoteknik_custom/models/solr/product_product.py index 667511b2..be5db814 100644 --- a/indoteknik_custom/models/solr/product_product.py +++ b/indoteknik_custom/models/solr/product_product.py @@ -67,10 +67,11 @@ class ProductProduct(models.Model): 'product_id_i': variant.id, 'template_id_i': variant.product_tmpl_id.id, 'image_s': ir_attachment.api_image('product.template', 'image_512', variant.product_tmpl_id.id), + 'image_carousel_s': [ir_attachment.api_image('image.carousel', 'image', carousel.product_id.id) for carousel in variant.product_tmpl_id.image_carousel_lines], 'image_mobile_s': ir_attachment.api_image('product.template', 'image_256', variant.product_tmpl_id.id), 'stock_total_f': variant.qty_stock_vendor, 'weight_f': variant.weight, - 'manufacture_id_i': variant.product_tmpl_id.x_manufacture.id or 0, + 'manufacture_id_i': variant.product_tmpl_id.x_manufacture.id or 0, 'manufacture_name_s': variant.product_tmpl_id.x_manufacture.x_name or '', 'manufacture_name': variant.product_tmpl_id.x_manufacture.x_name or '', 'image_promotion_1_s': ir_attachment.api_image('x_manufactures', 'image_promotion_1', variant.product_tmpl_id.x_manufacture.id), diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 8afff6e3..1f0ce918 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -26,7 +26,7 @@ class ProductTemplate(models.Model): 'function_name': function_name }) - @api.constrains('name', 'default_code', 'weight', 'x_manufacture', 'public_categ_ids', 'search_rank', 'search_rank_weekly', 'image_1920', 'unpublished') + @api.constrains('name', 'default_code', 'weight', 'x_manufacture', 'public_categ_ids', 'search_rank', 'search_rank_weekly', 'image_1920', 'unpublished','image_carousel_lines') def _create_solr_queue_sync_product_template(self): self._create_solr_queue('_sync_product_template_to_solr') @@ -94,6 +94,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), + 'image_carousel_s': [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.product_id.id) for carousel in template.image_carousel_lines], 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From a3150fba1ce5b57c57fcd6dae8b0b2c61308709b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 17:03:42 +0700 Subject: push --- indoteknik_custom/models/solr/product_product.py | 4 +++- indoteknik_custom/models/solr/product_template.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_product.py b/indoteknik_custom/models/solr/product_product.py index be5db814..d8bc3973 100644 --- a/indoteknik_custom/models/solr/product_product.py +++ b/indoteknik_custom/models/solr/product_product.py @@ -57,6 +57,8 @@ class ProductProduct(models.Model): is_in_bu = True if variant.qty_free_bandengan > 0 else False document = solr_model.get_doc('variants', variant.id) + + carousel = [ir_attachment.api_image('image.carousel', 'image', carousel.product_id.id) for carousel in variant.product_tmpl_id.image_carousel_lines], document.update({ 'id': variant.id, @@ -67,7 +69,7 @@ class ProductProduct(models.Model): 'product_id_i': variant.id, 'template_id_i': variant.product_tmpl_id.id, 'image_s': ir_attachment.api_image('product.template', 'image_512', variant.product_tmpl_id.id), - 'image_carousel_s': [ir_attachment.api_image('image.carousel', 'image', carousel.product_id.id) for carousel in variant.product_tmpl_id.image_carousel_lines], + 'image_carousel_s': [ir_attachment.api_image('image.carousel', 'image', carousel.id) for carousel in variant.product_tmpl_id.image_carousel_lines], 'image_mobile_s': ir_attachment.api_image('product.template', 'image_256', variant.product_tmpl_id.id), 'stock_total_f': variant.qty_stock_vendor, 'weight_f': variant.weight, diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 1f0ce918..80a57d66 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -85,6 +85,8 @@ class ProductTemplate(models.Model): cleaned_desc = BeautifulSoup(template.website_description or '', "html.parser").get_text() website_description = template.website_description if cleaned_desc else '' + carousel = [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines] + document = solr_model.get_doc('product', template.id) document.update({ "id": template.id, @@ -94,7 +96,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - 'image_carousel_s': [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.product_id.id) for carousel in template.image_carousel_lines], + 'image_carousel_s': [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From a91ffe639f73b8feac0e933524e92ed3e4168e69 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 17:09:02 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a017a090..b253090a 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1201,7 +1201,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: + if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date): raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From c48bd5f7fba6e75fe73a170ac3fbefbf3ac051e5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 17:23:04 +0700 Subject: push --- indoteknik_custom/models/solr/product_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 80a57d66..997415dc 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -96,7 +96,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - 'image_carousel_s': [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], + "image_carousel_s": [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 42671e5f4afd1d9646569791704bf9d66f265b5e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 22:19:01 +0700 Subject: trying to fix image_carousel --- indoteknik_custom/models/solr/product_template.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 997415dc..d9e83401 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -96,7 +96,10 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousel_s": [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], + 'image_carousel_s': {'set': [ + self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) + for carousel in template.image_carousel_lines + ]}, 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 986cf2eef7d5aafbdf8d936db5431f5ee1d1e830 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 22:24:00 +0700 Subject: push --- indoteknik_custom/models/solr/product_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index d9e83401..a1fc93cd 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -96,7 +96,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - 'image_carousel_s': {'set': [ + 'image_carousels': {'set': [ self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines ]}, -- cgit v1.2.3 From 3e0c3172cf929ee815dfdeccbd39a7ef91b5a152 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 3 May 2025 08:52:33 +0700 Subject: push image_carousels solr --- indoteknik_custom/models/solr/product_template.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index a1fc93cd..6c94efec 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -96,10 +96,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - 'image_carousels': {'set': [ - self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) - for carousel in template.image_carousel_lines - ]}, + "image_carousels": [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 7f6671fba5d872cfd0d80efadf667e039b95b0c5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 3 May 2025 09:54:51 +0700 Subject: push area --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b253090a..1291737e 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -161,8 +161,8 @@ class StockPicking(models.Model): @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: - district = record.real_shipping_id.kecamatan_id or '' - city = record.real_shipping_id.kota_id or '' + district = record.real_shipping_id.kecamatan_id.name or '' + city = record.real_shipping_id.kota_id.name or '' record.area_name = f"{district}, {city}".strip(', ') -- cgit v1.2.3 From 65f6e6ae297b535884c9563cfe1291f6c17157c9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 3 May 2025 10:22:38 +0700 Subject: push --- indoteknik_custom/models/commision.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 2f3789eb..32e81b9a 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -198,7 +198,8 @@ class CustomerCommision(models.Model): grouped_so_number = fields.Char(string='Group SO Number', compute='_compute_grouped_numbers') grouped_invoice_number = fields.Char(string='Group Invoice Number', compute='_compute_grouped_numbers') - sales_id = fields.Many2one('res.users', string="Sales", tracking=True) + sales_id = fields.Many2one('res.users', string="Sales", tracking=True, default=lambda self: self.env.user, + domain=lambda self: [('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman').id)]) date_approved_sales = fields.Datetime(string="Date Approved Sales", tracking=True) date_approved_marketing = fields.Datetime(string="Date Approved Marketing", tracking=True) -- cgit v1.2.3 From 55a29fb33823733e486a24e6d5747b58d531294a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:26:51 +0700 Subject: try solr image carousel --- indoteknik_custom/models/solr/product_template.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 6c94efec..4b7139bd 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -85,8 +85,7 @@ class ProductTemplate(models.Model): cleaned_desc = BeautifulSoup(template.website_description or '', "html.parser").get_text() website_description = template.website_description if cleaned_desc else '' - carousel = [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines] - + carousel_images = ', '.join([self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines]) document = solr_model.get_doc('product', template.id) document.update({ "id": template.id, @@ -96,7 +95,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousels": [self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines], + "image_carousels": carousel_images, 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 957246b5836f94f3663230545467407740f10736 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:46:05 +0700 Subject: push --- indoteknik_custom/models/solr/product_template.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 4b7139bd..20402c84 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -85,7 +85,12 @@ class ProductTemplate(models.Model): cleaned_desc = BeautifulSoup(template.website_description or '', "html.parser").get_text() website_description = template.website_description if cleaned_desc else '' - carousel_images = ', '.join([self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines]) + # carousel_images = ', '.join([self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines]) + carousel_images = [] + for carousel in template.image_carousel_lines: + image_url = self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) + if image_url: # Hanya tambahkan jika URL valid + carousel_images.append(image_url) document = solr_model.get_doc('product', template.id) document.update({ "id": template.id, @@ -95,7 +100,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousels": carousel_images, + "image_carousel_ss": carousel_images, 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From fd802e74a981f93e232aea18a00ebc6399547028 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:50:23 +0700 Subject: push --- indoteknik_custom/models/ir_attachment.py | 6 ++++-- indoteknik_custom/models/solr/product_template.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/ir_attachment.py b/indoteknik_custom/models/ir_attachment.py index 6417fa3f..1acd0848 100644 --- a/indoteknik_custom/models/ir_attachment.py +++ b/indoteknik_custom/models/ir_attachment.py @@ -20,9 +20,11 @@ class Attachment(models.Model): return True if attachment else False def api_image(self, model, field, id): - base_url = self.env['ir.config_parameter'].get_param('web.base.url') + if not id: + return None + base_url = self.env['ir.config_parameter'].get_param('web.base.url').rstrip('/') is_found = self.is_found(model, field, id) - return base_url + 'api/image/' + model + '/' + field + '/' + str(id) if is_found else '' + return f"{base_url}/api/image/{model}/{field}/{id}" if is_found else None def api_image_local(self, model, field, id): base_url = self.env['ir.config_parameter'].get_param('web.base.local_url') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 20402c84..42cab882 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -100,7 +100,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousel_ss": carousel_images, + "image_carousels_s": ' || '.join(carousel_images) # Gunakan pemisah yang unik 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 8d4c3a9b7673a17dddec8ece8ea7f75961c8ed54 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:51:34 +0700 Subject: push --- indoteknik_custom/models/solr/product_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 42cab882..4220f2f5 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -100,7 +100,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousels_s": ' || '.join(carousel_images) # Gunakan pemisah yang unik + "image_carousels_s": ' || '.join(carousel_images), # Gunakan pemisah yang unik 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 86cccc64c6f67b031a1bc345aae7922d83e021ea Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 09:58:48 +0700 Subject: push --- indoteknik_custom/models/ir_attachment.py | 6 ++---- indoteknik_custom/models/solr/product_template.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/ir_attachment.py b/indoteknik_custom/models/ir_attachment.py index 1acd0848..6417fa3f 100644 --- a/indoteknik_custom/models/ir_attachment.py +++ b/indoteknik_custom/models/ir_attachment.py @@ -20,11 +20,9 @@ class Attachment(models.Model): return True if attachment else False def api_image(self, model, field, id): - if not id: - return None - base_url = self.env['ir.config_parameter'].get_param('web.base.url').rstrip('/') + base_url = self.env['ir.config_parameter'].get_param('web.base.url') is_found = self.is_found(model, field, id) - return f"{base_url}/api/image/{model}/{field}/{id}" if is_found else None + return base_url + 'api/image/' + model + '/' + field + '/' + str(id) if is_found else '' def api_image_local(self, model, field, id): base_url = self.env['ir.config_parameter'].get_param('web.base.local_url') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 4220f2f5..20402c84 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -100,7 +100,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousels_s": ' || '.join(carousel_images), # Gunakan pemisah yang unik + "image_carousel_ss": carousel_images, 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 982e2b966385965328aafaa8607ca31d811874d9 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 5 May 2025 10:43:10 +0700 Subject: date reserved bu/out = validate date bu/pick --- indoteknik_custom/models/stock_picking.py | 505 +++++++++++++++++------------- 1 file changed, 279 insertions(+), 226 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1291737e..ce1399fe 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -15,22 +15,26 @@ import requests import time import logging import re + _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" _biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" + + # _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" - - + class StockPicking(models.Model): _inherit = 'stock.picking' _order = 'final_seq ASC' - konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True, copy=False) + konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True, + copy=False) scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True, copy=False) check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True, copy=False) - - check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, copy=False) + + check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, + copy=False) barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') account_id = fields.Many2one('account.account', string='Account') @@ -89,18 +93,23 @@ class StockPicking(models.Model): approval_status = fields.Selection([ ('pengajuan1', 'Approval Accounting'), ('approved', 'Approved'), - ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Internal Use") + ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3, + help="Approval Status untuk Internal Use") approval_receipt_status = fields.Selection([ ('pengajuan1', 'Approval Logistic'), ('approved', 'Approved'), - ], string='Approval Receipt Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Receipt") + ], string='Approval Receipt Status', readonly=True, copy=False, index=True, tracking=3, + help="Approval Status untuk Receipt") approval_return_status = fields.Selection([ ('pengajuan1', 'Approval Finance'), ('approved', 'Approved'), - ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") - date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, copy=False) + ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, + help="Approval Status untuk Return") + date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', + help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, + copy=False) note_logistic = fields.Selection([ ('wait_so_together', 'Tunggu SO Barengan'), ('not_paid', 'Customer belum bayar'), @@ -110,7 +119,8 @@ class StockPicking(models.Model): ('expedition_closed', 'Eskpedisi belum buka') ], string='Note Logistic', help='jika field ini diisi maka tidak akan dihitung ke lead time') waybill_id = fields.One2many(comodel_name='airway.bill', inverse_name='do_id', string='Airway Bill') - purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', string="Purchase Representative") + purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', + string="Purchase Representative") carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method') shipping_status = fields.Char(string='Shipping Status', compute="_compute_shipping_status") date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False) @@ -131,9 +141,9 @@ class StockPicking(models.Model): ('invoiced', 'Fully Invoiced'), ('to invoice', 'To Invoice'), ('no', 'Nothing to Invoice') - ], string='Invoice Status', related="sale_id.invoice_status") + ], string='Invoice Status', related="sale_id.invoice_status") note_return = fields.Text(string="Note Return", help="Catatan untuk kirim barang kembali") - + state_reserve = fields.Selection([ ('waiting', 'Waiting For Fullfilment'), ('ready', 'Ready to Ship'), @@ -146,7 +156,8 @@ class StockPicking(models.Model): ('waiting', 'Waiting For Approve by MD'), ('pending', 'Pending (perlu koordinasi dengan MD)'), ('done', 'Approve by MD'), - ], string='Approval MD Gudang Selisih', tracking=True, copy=False, help="The current state of the MD Approval transfer barang from gudang selisih.") + ], string='Approval MD Gudang Selisih', tracking=True, copy=False, + help="The current state of the MD Approval transfer barang from gudang selisih.") # show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") # def _compute_show_state_approve_md(self): @@ -158,13 +169,13 @@ class StockPicking(models.Model): linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) area_name = fields.Char(string="Area", compute="_compute_area_name") + @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: district = record.real_shipping_id.kecamatan_id.name or '' city = record.real_shipping_id.kota_id.name or '' record.area_name = f"{district}, {city}".strip(', ') - # def write(self, vals): # if 'linked_manual_bu_out' in vals: @@ -189,7 +200,7 @@ class StockPicking(models.Model): # picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) # picking.state_packing = 'packing_done' # return record - + @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli') def _compute_total_mapping_koli(self): for record in self: @@ -209,8 +220,10 @@ class StockPicking(models.Model): for picking in self: picking.dokumen_pengiriman = picking.partner_id.dokumen_pengiriman_input - dokumen_tanda_terima = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_tanda_terima) - dokumen_pengiriman = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_pengiriman) + dokumen_tanda_terima = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang', + readonly=True, compute=_compute_dokumen_tanda_terima) + dokumen_pengiriman = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang', readonly=True, + compute=_compute_dokumen_pengiriman) # Envio Tracking Section envio_id = fields.Char(string="Envio ID", readonly=True) @@ -255,8 +268,10 @@ class StockPicking(models.Model): # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) final_seq = fields.Float(string='Remaining Time') - shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') - state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') + shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', + related='sale_id.carrier_id') + state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], + string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') @@ -266,10 +281,10 @@ class StockPicking(models.Model): if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'): kirim_date = fields.Datetime.from_string(record.last_update_date_doc_kirim) now = fields.Datetime.now() - + deadline = kirim_date + timedelta(days=1) deadline = deadline.replace(hour=10, minute=0, second=0) - + if now > deadline: raise ValidationError( _("Anda tidak dapat mengubah Tanggal Kirim setelah jam 10:00 pada hari berikutnya!") @@ -281,12 +296,15 @@ class StockPicking(models.Model): rec.calculate_line_no() if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name and rec.partner_id.id != 96868: - invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') + invoice = self.env['account.move'].search( + [('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], + limit=1, order='create_date desc') if invoice and not self.env.context.get('active_model') == 'stock.picking': rec._check_date_doc_kirim_modification() if rec.date_doc_kirim != invoice.invoice_date and not self.env.context.get('from_button_approve'): - get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) + get_approval_invoice_date = self.env['approval.invoice.date'].search( + [('picking_id', '=', rec.id), ('state', '=', 'draft')], limit=1) if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': get_approval_invoice_date.date_doc_do = rec.date_doc_kirim @@ -306,12 +324,13 @@ class StockPicking(models.Model): return { 'type': 'ir.actions.client', 'tag': 'display_notification', - 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, + 'params': {'title': 'Notification', + 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', + 'next': {'type': 'ir.actions.act_window_close'}}, } - + rec.last_update_date_doc_kirim = datetime.datetime.utcnow() - - + @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): now = datetime.datetime.utcnow() @@ -319,16 +338,18 @@ class StockPicking(models.Model): if len(picking.scan_koli_lines) > 0: if len(picking.scan_koli_lines) != picking.total_mapping_koli: raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli") - + picking.driver_departure_date = now @api.depends('total_so_koli') def _compute_total_so_koli(self): for picking in self: if picking.state == 'done': - picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '=', 'delivered')]) + picking.total_so_koli = self.env['sales.order.koli'].search_count( + [('picking_id.linked_out_picking_id', '=', picking.id), ('state', '=', 'delivered')]) else: - picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')]) + picking.total_so_koli = self.env['sales.order.koli'].search_count( + [('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')]) @api.depends('total_koli') def _compute_total_koli(self): @@ -356,21 +377,21 @@ class StockPicking(models.Model): 'koli_id': rec.id, }) else: - raise UserError('Tidak Bisa Mengubah Quantity Koli Karena Koli Dari Picking Ini Sudah Dipakai Di BU/OUT!') + raise UserError( + 'Tidak Bisa Mengubah Quantity Koli Karena Koli Dari Picking Ini Sudah Dipakai Di BU/OUT!') @api.onchange('quantity_koli') def _onchange_quantity_koli(self): self.check_koli_lines = [(5, 0, 0)] self.check_koli_lines = [(0, 0, { - 'koli': f"{self.name}/{str(i+1).zfill(3)}", + 'koli': f"{self.name}/{str(i + 1).zfill(3)}", 'picking_id': self.id, }) for i in range(int(self.quantity_koli))] - + def schduled_update_sequance(self): query = "SELECT update_sequance_stock_picking();" self.env.cr.execute(query) - - + # @api.depends('estimated_ready_ship_date', 'state') # def _callculate_sequance(self): # for record in self: @@ -379,9 +400,9 @@ class StockPicking(models.Model): # rts = record.estimated_ready_ship_date - waktu.now() # rts_days = rts.days # rts_hours = divmod(rts.seconds, 3600) - + # estimated_by_erts = rts.total_seconds() / 3600 - + # record.countdown_ready_to_ship = f"{rts_days} days, {rts_hours} hours" # record.countdown_hours = estimated_by_erts # else: @@ -391,7 +412,6 @@ class StockPicking(models.Model): # _logger.error(f"Error calculating sequance {record.id}: {str(e)}") # print(str(e)) # return { 'error': str(e) } - # @api.depends('estimated_ready_ship_date', 'state') # def _compute_countdown_hours(self): @@ -431,7 +451,7 @@ class StockPicking(models.Model): ('state', '=', 'done'), ('carrier_id', '=', 9), ('lalamove_order_id', '!=', False) - ]) + ]) for picking in pickings: try: order_id = picking.lalamove_order_id @@ -466,7 +486,7 @@ class StockPicking(models.Model): for stop in stops: pod = stop.get("POD", {}) if pod.get("status") == "DELIVERED": - image_url = pod.get("image") # Sesuaikan jika key berbeda + image_url = pod.get("image") # Sesuaikan jika key berbeda self.lalamove_image_url = image_url address = stop.get("address") @@ -487,7 +507,6 @@ class StockPicking(models.Model): else: raise UserError(f"Error {response.status_code}: {response.text}") - def _convert_to_wib(self, date_str): """ Mengonversi string waktu ISO 8601 ke format waktu Indonesia (WIB) @@ -573,7 +592,7 @@ class StockPicking(models.Model): picking.tracking_by = self.env.user.id ata_at_str = data.get("ata_at") envio_ata = self._convert_to_datetime(data.get("ata_at")) - + picking.driver_arrival_date = envio_ata if data.get("status") != 'delivered': picking.driver_arrival_date = False @@ -584,13 +603,13 @@ class StockPicking(models.Model): raise UserError(f"Kesalahan tidak terduga: {str(e)}") def action_send_to_biteship(self): - + if self.biteship_tracking_id: raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}") - + # Mencari data sale.order.line berdasarkan sale_id products = self.env['sale.order.line'].search([('order_id', '=', self.sale_id.id)]) - + # Fungsi untuk membangun items_data dari order lines def build_items_data(lines): return [{ @@ -612,7 +631,7 @@ class StockPicking(models.Model): ('order_id', '=', self.sale_id.id), ('product_id', '=', move_line.product_id.id) ], limit=1) - + if order_line: items_data_instant.append({ "name": order_line.product_id.name, @@ -623,7 +642,7 @@ class StockPicking(models.Model): }) payload = { - "reference_id " : self.sale_id.name, + "reference_id ": self.sale_id.name, "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', "shipper_organization": self.carrier_id.name, @@ -644,19 +663,20 @@ class StockPicking(models.Model): } # Cek jika pengiriman instant atau same_day - if self.sale_id.delivery_service_type and ("instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): + if self.sale_id.delivery_service_type and ( + "instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): payload.update({ - "origin_coordinate" :{ + "origin_coordinate": { "latitude": -6.3031123, - "longitude" : 106.7794934999 + "longitude": 106.7794934999 }, - "destination_coordinate" : { + "destination_coordinate": { "latitude": self.real_shipping_id.latitude, "longitude": self.real_shipping_id.longtitude, }, "items": items_data_instant }) - + api_key = _biteship_api_key headers = { "Authorization": f"Bearer {api_key}", @@ -664,7 +684,7 @@ class StockPicking(models.Model): } # Kirim request ke Biteship - response = requests.post(_biteship_url+'/orders', headers=headers, json=payload) + response = requests.post(_biteship_url + '/orders', headers=headers, json=payload) if response.status_code == 200: data = response.json() @@ -673,7 +693,7 @@ class StockPicking(models.Model): self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "") self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "") self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "") - + waybill_id = data.get("courier", {}).get("waybill_id", "") message = f"✅ Berhasil Order ke Biteship! Resi: {waybill_id}" if waybill_id else "⚠️ Order berhasil, tetapi tidak ada nomor resi." @@ -690,10 +710,10 @@ class StockPicking(models.Model): error_message = error_data.get("error", "Unknown error") error_code = error_data.get("code", "No code provided") raise UserError(f"Error saat mengirim ke Biteship: {error_message} (Code: {error_code})") - + @api.constrains('driver_departure_date') - def constrains_driver_departure_date(self): - if not self.date_doc_kirim: + def constrains_driver_departure_date(self): + if not self.date_doc_kirim: self.date_doc_kirim = self.driver_departure_date @api.constrains('arrival_time') @@ -721,32 +741,32 @@ class StockPicking(models.Model): if not self._context.get('darimana') == 'sale.order' and self.env.user.id not in users_in_group.mapped('id'): self.sale_id.unreserve_id = self.id return self._create_approval_notification('Logistic') - + res = super(StockPicking, self).do_unreserve() current_time = datetime.datetime.utcnow() self.date_unreserve = current_time - + return res - + def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'internal'), ('name', 'ilike', 'BU/PICK/'), ]) - + for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ ('sale_order_id', '=', picking.sale_id.id) ]) - + picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' - + self.check_state_reserve_backorder() def check_state_reserve_backorder(self): @@ -756,29 +776,29 @@ class StockPicking(models.Model): ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) - + for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ ('sale_order_id', '=', picking.sale_id.id) ]) - + picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' - + def _create_approval_notification(self, approval_role): title = 'Warning' message = f'Butuh approval sales untuk unreserved' return self._create_notification_action(title, message) - + def _create_notification_action(self, title, message): return { 'type': 'ir.actions.client', 'tag': 'display_notification', - 'params': { 'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'} }, + 'params': {'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'}}, } def _compute_shipping_status(self): @@ -788,7 +808,7 @@ class StockPicking(models.Model): status = 'shipment' elif rec.driver_departure_date and (rec.sj_return_date or rec.driver_arrival_date): status = 'completed' - + rec.shipping_status = status def action_create_invoice_from_mr(self): @@ -796,10 +816,10 @@ class StockPicking(models.Model): """ if not self.env.user.is_accounting: raise UserError('Hanya Accounting yang bisa membuat Bill') - + precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') - #custom here + # custom here po = self.env['purchase.order'].search([ ('name', '=', self.group_id.name) ]) @@ -816,24 +836,29 @@ class StockPicking(models.Model): invoice_vals = order._prepare_invoice() # Invoice line values (keep only necessary sections). for line in self.move_ids_without_package: - po_line = self.env['purchase.order.line'].search([('order_id', '=', po.id), ('product_id', '=', line.product_id.id)], limit=1) + po_line = self.env['purchase.order.line'].search( + [('order_id', '=', po.id), ('product_id', '=', line.product_id.id)], limit=1) qty = line.product_uom_qty if po_line.display_type == 'line_section': pending_section = line continue if not float_is_zero(po_line.qty_to_invoice, precision_digits=precision): if pending_section: - invoice_vals['invoice_line_ids'].append((0, 0, pending_section._prepare_account_move_line_from_mr(po_line, qty))) + invoice_vals['invoice_line_ids'].append( + (0, 0, pending_section._prepare_account_move_line_from_mr(po_line, qty))) pending_section = None - invoice_vals['invoice_line_ids'].append((0, 0, line._prepare_account_move_line_from_mr(po_line, qty))) + invoice_vals['invoice_line_ids'].append( + (0, 0, line._prepare_account_move_line_from_mr(po_line, qty))) invoice_vals_list.append(invoice_vals) if not invoice_vals_list: - raise UserError(_('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.')) + raise UserError( + _('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.')) # 2) group by (company_id, partner_id, currency_id) for batch creation new_invoice_vals_list = [] - for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: (x.get('company_id'), x.get('partner_id'), x.get('currency_id'))): + for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: ( + x.get('company_id'), x.get('partner_id'), x.get('currency_id'))): origins = set() payment_refs = set() refs = set() @@ -863,7 +888,8 @@ class StockPicking(models.Model): # 4) Some moves might actually be refunds: convert them if the total amount is negative # We do this after the moves have been created since we need taxes, etc. to know if the total # is actually negative or not - moves.filtered(lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note() + moves.filtered( + lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note() return self.action_view_invoice_from_mr(moves) @@ -926,7 +952,7 @@ class StockPicking(models.Model): # for stock_move_line in stock_move_lines: # if stock_move_line.picking_id.state not in list_state: # continue - # raise UserError('Sudah pernah dikirim kalender') + # raise UserError('Sudah pernah dikirim kalender') for pick in self: if not pick.is_internal_use: @@ -952,18 +978,20 @@ class StockPicking(models.Model): if self.picking_type_code == 'outgoing': if self.env.user.id in [3988, 3401, 20] or ( - self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin + self.env.user.has_group( + 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin ): action['context'] = {'picking_ids': [x.id for x in self]} return action - elif not self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin: + elif not self.env.user.has_group( + 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin: raise UserError('Harus Purchasing yang Ask Return') else: raise UserError('Harus Sales Admin yang Ask Return') elif self.picking_type_code == 'incoming': if self.env.user.has_group('indoteknik_custom.group_role_purchasing') or ( - self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin + self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin ): action['context'] = {'picking_ids': [x.id for x in self]} return action @@ -973,7 +1001,7 @@ class StockPicking(models.Model): raise UserError('Harus Purchasing yang Ask Return') def calculate_line_no(self): - + for picking in self: name = picking.group_id.name for move in picking.move_ids_without_package: @@ -996,10 +1024,10 @@ class StockPicking(models.Model): def _compute_summary_qty(self): for picking in self: sum_qty_detail = sum_qty_operation = count_line_detail = count_line_operation = 0 - for detail in picking.move_line_ids_without_package: # detailed operations + for detail in picking.move_line_ids_without_package: # detailed operations sum_qty_detail += detail.qty_done count_line_detail += 1 - for operation in picking.move_ids_without_package: # operations + for operation in picking.move_ids_without_package: # operations sum_qty_operation += operation.product_uom_qty count_line_operation += 1 picking.summary_qty_detail = sum_qty_detail @@ -1021,13 +1049,13 @@ class StockPicking(models.Model): ]) if ( - self.picking_type_id.id == 29 - and quant - and line.location_id.id == bu_location_id - and quant.inventory_quantity < line.product_uom_qty + self.picking_type_id.id == 29 + and quant + and line.location_id.id == bu_location_id + and quant.inventory_quantity < line.product_uom_qty ): raise UserError('Quantity reserved lebih besar dari quantity onhand di product') - + def check_qty_done_stock(self): for line in self.move_line_ids_without_package: def check_qty_per_inventory(self, product, location): @@ -1040,10 +1068,11 @@ class StockPicking(models.Model): return quant.quantity return 0 - + qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) if line.qty_done > qty_onhand: raise UserError('Quantity Done melebihi Quantity Onhand') + def button_state_approve_md(self): group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) @@ -1068,7 +1097,8 @@ class StockPicking(models.Model): group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) active_model = self.env.context.get('active_model') - if self.location_id.id == 47 and self.env.user.id not in users_in_group.mapped('id') and self.state_approve_md != 'done': + if self.location_id.id == 47 and self.env.user.id not in users_in_group.mapped( + 'id') and self.state_approve_md != 'done': self.state_approve_md = 'waiting' if self.state_approve_md != 'pending' else 'pending' self.env.cr.commit() raise UserError("Transfer dari gudang selisih harus di approve MD, Hubungi MD agar bisa di Validate") @@ -1076,37 +1106,36 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - - if (len(self.konfirm_koli_lines) == 0 - and 'BU/OUT/' in self.name - and self.picking_type_code == 'outgoing' - and self.create_date > threshold_datetime - and not self.so_lama): + if (len(self.konfirm_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and self.create_date > threshold_datetime + and not self.so_lama): raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) - if (len(self.scan_koli_lines) == 0 - and 'BU/OUT/' in self.name - and self.picking_type_code == 'outgoing' - and self.create_date > threshold_datetime - and not self.so_lama): + if (len(self.scan_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and self.create_date > threshold_datetime + and not self.so_lama): raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) - + # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': # raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) - + if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada koli! Harap periksa kembali.")) - + if not self.linked_manual_bu_out and 'BU/PICK/' in self.name: raise UserError(_("Isi BU Out terlebih dahulu!")) - + if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada Check Product! Harap periksa kembali.")) - + if self.total_koli > self.total_so_koli: - raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") - % (self.total_koli, self.t1otal_so_koli)) - + raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") + % (self.total_koli, self.t1otal_so_koli)) + if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") @@ -1128,7 +1157,7 @@ class StockPicking(models.Model): if self.is_internal_use and not self.env.user.is_accounting: raise UserError("Harus di Approve oleh Accounting") - + if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: raise UserError("Harus di Approve oleh Logistik") @@ -1163,6 +1192,16 @@ class StockPicking(models.Model): self.final_seq = 0 self.set_picking_code_out() self.send_koli_to_so() + + if (self.state_reserve == 'done' and self.picking_type_code == 'internal' and 'BU/PICK/' in self.name + and self.linked_manual_bu_out): + if not self.linked_manual_bu_out.date_reserved: + current_datetime = datetime.datetime.utcnow() + self.linked_manual_bu_out.date_reserved = current_datetime + self.linked_manual_bu_out.message_post( + body=f"Date Reserved diisi secara otomatis dari validasi BU/PICK {self.name}" + ) + if not self.env.context.get('skip_koli_check'): for picking in self: if picking.sale_id: @@ -1172,7 +1211,8 @@ class StockPicking(models.Model): missing_koli_ids = set(all_koli_ids) - set(scanned_koli_ids) if len(missing_koli_ids) > 0 and picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: - missing_koli_names = picking.sale_id.koli_lines.filtered(lambda k: k.id in missing_koli_ids and k.state != 'delivered').mapped('display_name') + missing_koli_names = picking.sale_id.koli_lines.filtered( + lambda k: k.id in missing_koli_ids and k.state != 'delivered').mapped('display_name') missing_koli_list = "\n".join(f"- {name}" for name in missing_koli_names) # Buat wizard modal warning @@ -1190,36 +1230,37 @@ class StockPicking(models.Model): } self.send_mail_bills() return res - + def check_invoice_date(self): for picking in self: if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name or picking.partner_id.id == 96868: continue - - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id), ('state','not in',['draft','cancel'])], limit=1) - + + invoice = self.env['account.move'].search( + [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel'])], limit=1) + if not invoice: continue - + if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date): raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") - + picking_date = fields.Date.to_date(picking.date_doc_kirim) invoice_date = fields.Date.to_date(invoice.invoice_date) - + if picking_date != invoice_date and picking.update_date_doc_kirim_add: raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % ( picking_date.strftime('%d-%m-%Y'), invoice_date.strftime('%d-%m-%Y') )) - + def set_picking_code_out(self): for picking in self: # Check if picking meets criteria is_bu_pick = picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name if not is_bu_pick: continue - + # Find matching outgoing transfers bu_out_transfers = self.search([ ('name', 'like', 'BU/OUT/%'), @@ -1228,11 +1269,10 @@ class StockPicking(models.Model): ('picking_code', '=', False), ('state', 'not in', ['done', 'cancel']) ]) - + # Assign sequence code to each matching transfer for transfer in bu_out_transfers: transfer.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code') - def check_koli(self): for picking in self: @@ -1240,7 +1280,7 @@ class StockPicking(models.Model): for koli_lines in picking.scan_koli_lines: if koli_lines.koli_id.sale_order_id != sale_id: raise UserError('Koli tidak sesuai') - + def send_koli_to_so(self): for picking in self: if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name: @@ -1257,7 +1297,7 @@ class StockPicking(models.Model): 'picking_id': picking.id, 'koli_id': koli_line.id }) - + if picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: if picking.state == 'done': for koli_line in picking.scan_koli_lines: @@ -1265,7 +1305,7 @@ class StockPicking(models.Model): ('sale_order_id', '=', picking.sale_id.id), ('koli_id', '=', koli_line.koli_id.koli_id.id) ], limit=1) - + existing_koli.state = 'delivered' def check_qty_done_stock(self): @@ -1280,7 +1320,7 @@ class StockPicking(models.Model): return quant.quantity return 0 - + qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) if line.qty_done > qty_onhand: raise UserError('Quantity Done melebihi Quantity Onhand') @@ -1337,11 +1377,14 @@ class StockPicking(models.Model): return True def action_cancel(self): - if not self.env.user.is_logistic_approver and (self.env.context.get('active_model') == 'stock.picking' or self.env.context.get('active_model') == 'stock.picking.type'): + if not self.env.user.is_logistic_approver and ( + self.env.context.get('active_model') == 'stock.picking' or self.env.context.get( + 'active_model') == 'stock.picking.type'): if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") - - if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing': + + if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group( + 'indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing': raise UserError("Button ini hanya untuk Logistik") res = super(StockPicking, self).action_cancel() @@ -1351,7 +1394,7 @@ class StockPicking(models.Model): def create(self, vals): self._use_faktur(vals) records = super(StockPicking, self).create(vals) - + # Panggil sync_sale_line setelah record dibuat # records.sync_sale_line(vals) return records @@ -1373,8 +1416,8 @@ class StockPicking(models.Model): def write(self, vals): if 'linked_manual_bu_out' in vals: for record in self: - if (record.picking_type_code == 'internal' - and 'BU/PICK/' in record.name): + if (record.picking_type_code == 'internal' + and 'BU/PICK/' in record.name): # Jika menghapus referensi (nilai di-set False/None) if record.linked_manual_bu_out and not vals['linked_manual_bu_out']: record.linked_manual_bu_out.state_packing = 'not_packing' @@ -1399,7 +1442,8 @@ class StockPicking(models.Model): if name_to_modify.startswith('BU/INT'): new_name = name_to_modify.replace('BU/INT', 'BU/IN', 1) # Periksa apakah nama sudah ada - if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0: + if self.env['stock.picking'].search_count( + [('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0: new_name = f"{new_name}-DUP" vals['name'] = new_name return super(StockPicking, self).write(vals) @@ -1443,7 +1487,7 @@ class StockPicking(models.Model): def get_manifests(self): if self.waybill_id and len(self.waybill_id.manifest_ids) > 0: return [self.create_manifest_data(x.description, x.datetime) for x in self.waybill_id.manifest_ids] - + status_mapping = { 'pickup': { 'arrival': 'Sudah diambil', @@ -1468,7 +1512,7 @@ class StockPicking(models.Model): if not status: return manifest_datas - + if arrival_date or self.sj_return_date: manifest_datas.append(self.create_manifest_data(status['arrival'], arrival_date)) if departure_date: @@ -1479,14 +1523,14 @@ class StockPicking(models.Model): def get_tracking_detail(self): self.ensure_one() - + order = self.env['sale.order'].search([('name', '=', self.sale_id.name)], limit=1) response = { 'delivery_order': { 'name': self.name, 'carrier': self.carrier_id.name or '', - 'service' : order.delivery_service_type or '', + 'service': order.delivery_service_type or '', 'receiver_name': '', 'receiver_city': '' }, @@ -1498,74 +1542,76 @@ class StockPicking(models.Model): 'is_biteship': True if self.biteship_id else False, 'manifests': self.get_manifests() } - - if self.biteship_id : + + if self.biteship_id: histori = self.get_manifest_biteship() eta_start = order.date_order + timedelta(days=order.estimated_arrival_days_start) eta_end = order.date_order + timedelta(days=order.estimated_arrival_days) formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}" response['eta'] = formatted_eta - response['manifests'] = histori.get("manifests", []) - response['delivered'] = histori.get("delivered", False) or self.sj_return_date != False or self.driver_arrival_date != False + response['manifests'] = histori.get("manifests", []) + response['delivered'] = histori.get("delivered", + False) or self.sj_return_date != False or self.driver_arrival_date != False response['status'] = self._map_status_biteship(histori.get("delivered")) - + return response if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0: response['delivered'] = self.sj_return_date != False or self.driver_arrival_date != False return response - + response['delivery_order']['receiver_name'] = self.waybill_id.receiver_name response['delivery_order']['receiver_city'] = self.waybill_id.receiver_city response['delivery_status'] = self.waybill_id._get_history('delivery_status') response['delivered'] = self.waybill_id.delivered return response - + def get_manifest_biteship(self): api_key = _biteship_api_key headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } - - + manifests = [] - + try: - # Kirim request ke Biteship - response = requests.get(_biteship_url+'/trackings/'+self.biteship_tracking_id, headers=headers, json=manifests) + # Kirim request ke Biteship + response = requests.get(_biteship_url + '/trackings/' + self.biteship_tracking_id, headers=headers, + json=manifests) result = response.json() description = { - 'confirmed' : 'Indoteknik telah melakukan permintaan pick-up', - 'allocated' : 'Kurir akan melakukan pick-up pesanan', - 'picking_up' : 'Kurir sedang dalam perjalanan menuju lokasi pick-up', - 'picked' : 'Pesanan sudah di pick-up kurir '+result.get("courier", {}).get("name", ""), - 'on_hold' : 'Pesanan ditahan sementara karena masalah pengiriman', - 'dropping_off' : 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', - 'delivered' : 'Pesanan telah sampai dan diterima oleh '+result.get("destination", {}).get("contact_name", "") + 'confirmed': 'Indoteknik telah melakukan permintaan pick-up', + 'allocated': 'Kurir akan melakukan pick-up pesanan', + 'picking_up': 'Kurir sedang dalam perjalanan menuju lokasi pick-up', + 'picked': 'Pesanan sudah di pick-up kurir ' + result.get("courier", {}).get("name", ""), + 'on_hold': 'Pesanan ditahan sementara karena masalah pengiriman', + 'dropping_off': 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', + 'delivered': 'Pesanan telah sampai dan diterima oleh ' + result.get("destination", {}).get( + "contact_name", "") } - if(result.get('success') == True): + if (result.get('success') == True): history = result.get("history", []) status = result.get("status", "") - + for entry in reversed(history): manifests.append({ "status": re.sub(r'[^a-zA-Z0-9\s]', ' ', entry["status"]).lower().capitalize(), "datetime": self._convert_to_local_time(entry["updated_at"]), - "description": description[entry["status"]], + "description": description[entry["status"]], }) - + return { "manifests": manifests, "delivered": status } return manifests - except Exception as e : + except Exception as e: _logger.error(f"Error fetching Biteship order for picking {self.id}: {str(e)}") - return { 'error': str(e) } - + return {'error': str(e)} + def _convert_to_local_time(self, iso_date): try: dt_with_tz = waktu.fromisoformat(iso_date) @@ -1577,7 +1623,7 @@ class StockPicking(models.Model): return local_dt.strftime("%Y-%m-%d %H:%M:%S") except Exception as e: return str(e) - + def _map_status_biteship(self, status): status_mapping = { "confirmed": "pending", @@ -1591,7 +1637,7 @@ class StockPicking(models.Model): "delivered": "completed" } return status_mapping.get(status, "Hubungi Admin") - + def generate_eta_delivery(self): current_date = datetime.datetime.now() prepare_days = 3 @@ -1605,7 +1651,7 @@ class StockPicking(models.Model): fastest_eta = start_date + ead_datetime if not self.driver_departure_date and fastest_eta < current_date: fastest_eta = current_date + ead_datetime - + longest_days = 3 longest_eta = fastest_eta + datetime.timedelta(days=longest_days) @@ -1614,9 +1660,10 @@ class StockPicking(models.Model): formatted_fastest_eta = fastest_eta.strftime(format_time_fastest) formatted_longest_eta = longest_eta.strftime(format_time) - + return f'{formatted_fastest_eta} - {formatted_longest_eta}' - + + class CheckProduct(models.Model): _name = 'check.product' _description = 'Check Product' @@ -1639,7 +1686,7 @@ class CheckProduct(models.Model): def _onchange_code_product(self): if not self.code_product: return - + # Cari product berdasarkan default_code, barcode, atau barcode_box product = self.env['product.product'].search([ '|', @@ -1648,10 +1695,10 @@ class CheckProduct(models.Model): ('barcode', '=', self.code_product), ('barcode_box', '=', self.code_product) ], limit=1) - + if not product: raise UserError("Product tidak ditemukan") - + # Jika scan barcode_box, set quantity sesuai qty_pcs_box if product.barcode_box == self.code_product: self.product_id = product.id @@ -1660,7 +1707,7 @@ class CheckProduct(models.Model): # return { # 'warning': { # 'title': 'Info',8994175025871 - + # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}' # } # } @@ -1673,29 +1720,29 @@ class CheckProduct(models.Model): def unlink(self): # Get all affected pickings before deletion pickings = self.mapped('picking_id') - + # Store product_ids that will be deleted deleted_product_ids = self.mapped('product_id') - + # Perform the deletion result = super(CheckProduct, self).unlink() - + # After deletion, update moves for affected pickings for picking in pickings: # For products that were completely removed (no remaining check.product lines) remaining_product_ids = picking.check_product_lines.mapped('product_id') removed_product_ids = deleted_product_ids - remaining_product_ids - + # Set quantity_done to 0 for moves of completely removed products moves_to_reset = picking.move_ids_without_package.filtered( lambda move: move.product_id in removed_product_ids ) for move in moves_to_reset: move.quantity_done = 0.0 - + # Also sync remaining products in case their totals changed self._sync_check_product_to_moves(picking) - + return result @api.depends('quantity') @@ -1711,7 +1758,6 @@ class CheckProduct(models.Model): else: record.status = 'Done' - def create(self, vals): # Create the record record = super(CheckProduct, self).create(vals) @@ -1736,7 +1782,8 @@ class CheckProduct(models.Model): for product_id in picking.check_product_lines.mapped('product_id'): # Totalkan quantity dari semua baris check.product untuk product_id ini total_quantity = sum( - line.quantity for line in picking.check_product_lines.filtered(lambda line: line.product_id == product_id) + line.quantity for line in + picking.check_product_lines.filtered(lambda line: line.product_id == product_id) ) # Update quantity_done di move yang relevan moves = picking.move_ids_without_package.filtered(lambda move: move.product_id == product_id) @@ -1789,8 +1836,8 @@ class CheckProduct(models.Model): if not moves: raise UserError(( - "The product '%s' tidak ada di operations. " - ) % record.product_id.display_name) + "The product '%s' tidak ada di operations. " + ) % record.product_id.display_name) total_qty_in_moves = sum(moves.mapped('product_uom_qty')) @@ -1808,18 +1855,19 @@ class CheckProduct(models.Model): if total_quantity > total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand." - ) % (record.product_id.display_name)) + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total if record.quantity == total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand." - ) % (record.product_id.display_name)) + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) # Set the quantity to the entered value record.quantity = record.quantity + class BarcodeProduct(models.Model): _name = 'barcode.product' _description = 'Barcode Product' @@ -1841,7 +1889,7 @@ class BarcodeProduct(models.Model): if barcode_product: raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) - + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) if barcode_box: @@ -1855,7 +1903,8 @@ class BarcodeProduct(models.Model): record.product_id.barcode = record.barcode else: raise UserError('Barcode sudah terisi') - + + class CheckKoli(models.Model): _name = 'check.koli' _description = 'Check Koli' @@ -1885,7 +1934,8 @@ class CheckKoli(models.Model): check_index = list(all_checks).index(check) + 1 # Nomor urut check total_so_koli = len(all_checks) check.check_koli_progress = f"{check_index}/{total_so_koli}" if total_so_koli else "0/0" - + + class ScanKoli(models.Model): _name = 'scan.koli' _description = 'Scan Koli' @@ -1929,18 +1979,18 @@ class ScanKoli(models.Model): def _onchange_koli_compare_with_konfirm_koli(self): if not self.koli_id: return - + if not self.picking_id.konfirm_koli_lines: raise UserError(_('Mapping Koli Harus Diisi!')) - + koli_picking = self.koli_id.picking_id._origin - + konfirm_pick_ids = [ - line.pick_id._origin - for line in self.picking_id.konfirm_koli_lines + line.pick_id._origin + for line in self.picking_id.konfirm_koli_lines if line.pick_id ] - + if koli_picking not in konfirm_pick_ids: raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) @@ -1951,7 +2001,7 @@ class ScanKoli(models.Model): existing_koli = self.search([ ('picking_id', '=', record.picking_id.id), ('koli_id', '=', record.koli_id.id), - ('id', '!=', record.id) + ('id', '!=', record.id) ]) if existing_koli: raise ValidationError(f"⚠️ Koli '{record.koli_id.display_name}' sudah discan untuk picking ini!") @@ -1974,22 +2024,23 @@ class ScanKoli(models.Model): picking = self.env['stock.picking'].browse(picking_id) picking.linked_out_picking_id = False else: - raise UserError(_("Tidak dapat menghapus scan koli, karena masih ada scan koli lain yang tersisa untuk picking ini.")) - + raise UserError( + _("Tidak dapat menghapus scan koli, karena masih ada scan koli lain yang tersisa untuk picking ini.")) + for picking_id in picking_ids: self._reset_qty_done_if_no_scan(picking_id) - + # self.check_koli_not_balance() return super(ScanKoli, self).unlink() - @api.onchange('koli_id','scan_koli_progress') + @api.onchange('koli_id', 'scan_koli_progress') def onchange_koli_id(self): if not self.koli_id: return - + for scan in self: - if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: + if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: scan.koli_id.koli_id.reserved_id = scan.picking_id.id.origin scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin @@ -1998,7 +2049,7 @@ class ScanKoli(models.Model): if not scan.picking_id: scan.scan_koli_progress = "0/0" continue - + try: all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') if all_scans: @@ -2010,16 +2061,17 @@ class ScanKoli(models.Model): except Exception: # Fallback in case of any error scan.scan_koli_progress = "0/0" + @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): for scan in self.picking_id.scan_koli_lines: scan.koli_id.koli_id.reserved_id = scan.picking_id.id scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id - + total_scans = len(self.picking_id.scan_koli_lines) if total_scans != self.picking_id.total_so_koli: raise UserError(_("Jumlah scan koli tidak sama dengan total SO koli!")) - + # def check_koli_not_balance(self): # for scan in self: # total_scancs = self.env['scan.koli'].search_count([('picking_id', '=', scan.picking_id.id), ('id', '!=', scan.id)]) @@ -2033,22 +2085,22 @@ class ScanKoli(models.Model): source_koli_so = self.picking_id.group_id.id source_koli = self.koli_id.picking_id.group_id.id - + if source_koli_so != source_koli: raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) - + @api.constrains('koli_id') def _send_product_from_koli_id(self): if not self.koli_id: return - + koli_count_by_picking = defaultdict(int) for scan in self: koli_count_by_picking[scan.koli_id.picking_id.id] += 1 for picking_id, total_koli in koli_count_by_picking.items(): picking = self.env['stock.picking'].browse(picking_id) - + if total_koli == picking.quantity_koli: pick_moves = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) out_moves = self.env['stock.move.line'].search([('picking_id', '=', picking.linked_out_picking_id.id)]) @@ -2056,21 +2108,23 @@ class ScanKoli(models.Model): for pick_move in pick_moves: corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) if corresponding_out_move: - corresponding_out_move.qty_done += pick_move.qty_done + corresponding_out_move.qty_done += pick_move.qty_done def _reset_qty_done_if_no_scan(self, picking_id): - product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) + product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) + + for move in product_bu_pick: + product_bu_out = self.env['stock.move.line'].search( + [('picking_id', '=', self.picking_id.id), ('product_id', '=', move.product_id.id)]) + for bu_out in product_bu_out: + bu_out.qty_done -= move.qty_done + # if remaining_scans == 0: + # picking = self.env['stock.picking'].browse(picking_id) + # picking.move_line_ids_without_package.write({'qty_done': 0}) + # picking.message_post(body=f"⚠ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.") - for move in product_bu_pick: - product_bu_out = self.env['stock.move.line'].search([('picking_id', '=', self.picking_id.id), ('product_id', '=', move.product_id.id)]) - for bu_out in product_bu_out: - bu_out.qty_done -= move.qty_done - # if remaining_scans == 0: - # picking = self.env['stock.picking'].browse(picking_id) - # picking.move_line_ids_without_package.write({'qty_done': 0}) - # picking.message_post(body=f"⚠ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.") + # return remaining_scans - # return remaining_scans class KonfirmKoli(models.Model): _name = 'konfirm.koli' @@ -2099,7 +2153,8 @@ class KonfirmKoli(models.Model): if exist: raise UserError(f"⚠️ '{rec.pick_id.display_name}' sudah discan untuk picking ini!") - + + class WarningModalWizard(models.TransientModel): _name = 'warning.modal.wizard' _description = 'Peringatan Koli Belum Diperiksa' @@ -2112,5 +2167,3 @@ class WarningModalWizard(models.TransientModel): if self.picking_id: return self.picking_id.with_context(skip_koli_check=True).button_validate() return {'type': 'ir.actions.act_window_close'} - - -- cgit v1.2.3 From b00d3caf6cd21ae74872031536ceb2f1ff570316 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 5 May 2025 11:05:13 +0700 Subject: (andri) NPWP di SO menjadi readonly --- indoteknik_custom/models/sale_order.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4c48684d..baa72dc0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -204,7 +204,7 @@ class SaleOrder(models.Model): ('nonpkp', 'Non PKP') ], required=True) sppkp = fields.Char(string="SPPKP", required=True, tracking=True) - npwp = fields.Char(string="NPWP", required=True, tracking=True) + npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_npwp') purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total') voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False) applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False) @@ -995,6 +995,12 @@ class SaleOrder(models.Model): # return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)] + @api.depends('partner_id') + def _compute_npwp(self): + for order in self: + partner = order.partner_id.parent_id or order.partner_id + order.npwp = partner.npwp + @api.onchange('partner_id') def onchange_partner_contact(self): parent_id = self.partner_id.parent_id -- cgit v1.2.3 From 4fc562965d80b3315e58568c42075edf154256dc Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 11:31:32 +0700 Subject: refactor ask approval retur --- indoteknik_custom/models/stock_picking.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1291737e..0b78706c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -947,6 +947,8 @@ class StockPicking(models.Model): if self.env.user.is_accounting: pick.approval_return_status = 'approved' continue + else: + pick.approval_return_status = 'pengajuan1' action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard') -- cgit v1.2.3 From 921bbc2f0b5b82945aebc11e96ba3847c6f2904d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 5 May 2025 11:48:57 +0700 Subject: (andri) Make the SPPKP & Customer Type columns read-only in SO and ensure the data (along with NPWP) is connected to the contact --- indoteknik_custom/models/sale_order.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index baa72dc0..0711e33a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -202,9 +202,9 @@ class SaleOrder(models.Model): customer_type = fields.Selection([ ('pkp', 'PKP'), ('nonpkp', 'Non PKP') - ], required=True) - sppkp = fields.Char(string="SPPKP", required=True, tracking=True) - npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_npwp') + ], required=True, compute='_compute_partner_field') + sppkp = fields.Char(string="SPPKP", required=True, tracking=True, compute='_compute_partner_field') + npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_partner_field') purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total') voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False) applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False) @@ -994,12 +994,13 @@ class SaleOrder(models.Model): # return [('id', 'not in', order_ids)] # return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)] - @api.depends('partner_id') - def _compute_npwp(self): + def _compute_partner_field(self): for order in self: partner = order.partner_id.parent_id or order.partner_id - order.npwp = partner.npwp + order.npwp = partner.npwp + order.sppkp = partner.sppkp + order.customer_type = partner.customer_type @api.onchange('partner_id') def onchange_partner_contact(self): -- cgit v1.2.3 From fcc0596b649ef53a6280530b60c89a2a0dc0ebec Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 17:01:05 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0b78706c..fadcc3c2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1203,7 +1203,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date): + if not picking.so_lama and invoice and (not picking.date_doc_kirim or not invoice.invoice_date): raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From 5b78ec72f001ad0d5ccde1269d1ec86418fb7339 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 17:02:16 +0700 Subject: push --- indoteknik_custom/models/sale_order.py | 47 ++++++++++++++++++++++++++++++++++ indoteknik_custom/models/stock_move.py | 1 + 2 files changed, 48 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b0e17a3a..8af34c75 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -247,6 +247,51 @@ class SaleOrder(models.Model): ) nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") + hold_outgoing = fields.Boolean('Hold Outgoing SO') + state_ask_cancel = fields.Selection([ + ('hold', 'Hold'), + ('approve', 'Approve') + ], tracking=True, string='State Cancel', copy=False) + + def ask_retur_cancel_purchasing(self): + for rec in self: + if self.env.user.has_group('indoteknik_custom.group_role_purchasing'): + rec.state_ask_cancel = 'approve' + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Persetujuan Diberikan', + 'message': 'Proses cancel sudah disetujui', + 'type': 'success', + 'sticky': True + } + } + else: + rec.state_ask_cancel = 'hold' + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Menunggu Persetujuan', + 'message': 'Tim Purchasing akan memproses permintaan Anda', + 'type': 'warning', + 'sticky': False + } + } + + def hold_unhold_qty_outgoing_so(self): + pickings = self.env['stock.picking'].search([('sale_id', '=', self.id), ('state', 'not in', ['cancel','done']), ('name', 'ilike', 'BU/PICK/%')]) + products = self.order_line.mapped('product_id') + for picking in pickings: + for line in picking.move_ids_without_package: + if line.product_id in products: + if line.hold_outgoingg == True: + line.hold_outgoingg = False + self.hold_outgoing = False + else: + line.hold_outgoingg = True + self.hold_outgoing = True def _validate_uniform_taxes(self): for order in self: @@ -1392,6 +1437,8 @@ class SaleOrder(models.Model): def action_cancel(self): # TODO stephan prevent cancel if have invoice, do, and po + if self.state_ask_cancel != 'approve': + raise UserError("Anda harus approval purchasing terlebih dahulu") main_parent = self.partner_id.get_main_parent() if self._name != 'sale.order': return super(SaleOrder, self).action_cancel() diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 3c765244..90ab30a4 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -14,6 +14,7 @@ class StockMove(models.Model): qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') + hold_outgoingg = fields.Boolean('Hold Outgoing', default=False) # @api.model_create_multi # def create(self, vals_list): -- cgit v1.2.3 From e16c4fd64143db43eff45d39789a1e59d64f1d34 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 6 May 2025 09:19:46 +0700 Subject: push --- indoteknik_custom/models/solr/product_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 20402c84..c4aefe19 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -100,7 +100,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), - "image_carousel_ss": carousel_images, + "image_carousel_ss": carousel_images if carousel_images else [], 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, -- cgit v1.2.3 From 08fb1ce211c3bc11e6ff0ea6ff37e0be8b7c2b31 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 6 May 2025 13:55:37 +0700 Subject: qty hold outgoing and ask cancel purchasing --- indoteknik_custom/models/sale_order.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8af34c75..aa4ece13 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -281,17 +281,10 @@ class SaleOrder(models.Model): } def hold_unhold_qty_outgoing_so(self): - pickings = self.env['stock.picking'].search([('sale_id', '=', self.id), ('state', 'not in', ['cancel','done']), ('name', 'ilike', 'BU/PICK/%')]) - products = self.order_line.mapped('product_id') - for picking in pickings: - for line in picking.move_ids_without_package: - if line.product_id in products: - if line.hold_outgoingg == True: - line.hold_outgoingg = False - self.hold_outgoing = False - else: - line.hold_outgoingg = True - self.hold_outgoing = True + if self.hold_outgoing == True: + self.hold_outgoing = False + else: + self.hold_outgoing = True def _validate_uniform_taxes(self): for order in self: -- cgit v1.2.3 From 2bb2ead5a6ee0a76a088f7e522cabc74ac8809be Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 6 May 2025 15:30:04 +0700 Subject: push --- indoteknik_custom/models/sale_order.py | 40 ++++++++++++++++++++--------- indoteknik_custom/models/sale_order_line.py | 19 ++++++++++++++ 2 files changed, 47 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 1e744f18..0d86019e 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -133,8 +133,9 @@ class SaleOrder(models.Model): reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines') order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True) total_margin = fields.Float('Total Margin', compute='_compute_total_margin', help="Total Margin in Sales Order Header") + total_before_margin = fields.Float('Total Before Margin', compute='_compute_total_before_margin', help="Total Margin in Sales Order Header") total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', help="Total % Margin in Sales Order Header") - total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header") + total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header", compute='_compute_total_margin_excl_third_party') approval_status = fields.Selection([ ('pengajuan1', 'Approval Manager'), ('pengajuan2', 'Approval Pimpinan'), @@ -302,6 +303,16 @@ class SaleOrder(models.Model): ('approve', 'Approve') ], tracking=True, string='State Cancel', copy=False) + def _compute_total_margin_excl_third_party(self): + for order in self: + if order.amount_untaxed == 0: + order.total_margin_excl_third_party = 0 + continue + + # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2) + order.total_margin_excl_third_party = round((order.total_before_margin / (order.amount_untaxed)) * 100, 2) + # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2) + def ask_retur_cancel_purchasing(self): for rec in self: if self.env.user.has_group('indoteknik_custom.group_role_purchasing'): @@ -345,17 +356,17 @@ class SaleOrder(models.Model): if len(tax_sets) > 1: raise ValidationError("Semua produk dalam Sales Order harus memiliki kombinasi pajak yang sama.") - @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') - def _check_total_margin_excl_third_party(self): - for rec in self: - if rec.fee_third_party == 0 and rec.total_margin_excl_third_party != rec.total_percent_margin: - # Gunakan direct SQL atau flag context untuk menghindari rekursi - self.env.cr.execute(""" - UPDATE sale_order - SET total_margin_excl_third_party = %s - WHERE id = %s - """, (rec.total_percent_margin, rec.id)) - self.invalidate_cache() + # @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') + # def _check_total_margin_excl_third_party(self): + # for rec in self: + # if rec.fee_third_party == 0 and rec.total_margin_excl_third_party != rec.total_percent_margin: + # # Gunakan direct SQL atau flag context untuk menghindari rekursi + # self.env.cr.execute(""" + # UPDATE sale_order + # SET total_margin_excl_third_party = %s + # WHERE id = %s + # """, (rec.total_percent_margin, rec.id)) + # self.invalidate_cache() @api.constrains('shipping_option_id') def _check_shipping_option(self): @@ -1607,6 +1618,11 @@ class SaleOrder(models.Model): order.total_margin = total_margin + def _compute_total_before_margin(self): + for order in self: + total_before_margin = sum(line.item_before_margin for line in order.order_line if line.product_id) + order.total_before_margin = total_before_margin + def _compute_total_percent_margin(self): for order in self: if order.amount_untaxed == 0: diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index aed95aab..2450abd4 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -6,6 +6,7 @@ from datetime import datetime, timedelta class SaleOrderLine(models.Model): _inherit = 'sale.order.line' item_margin = fields.Float('Margin', compute='compute_item_margin', help="Total Margin in Sales Order Header") + item_before_margin = fields.Float('Before Margin', compute='compute_item_before_margin', help="Total Margin in Sales Order Header") item_percent_margin = fields.Float('%Margin', compute='compute_item_margin', help="Total % Margin in Sales Order Header") initial_discount = fields.Float('Initial Discount') vendor_id = fields.Many2one( @@ -146,6 +147,24 @@ class SaleOrderLine(models.Model): if not line.margin_md: line.margin_md = line.item_percent_margin + def compute_item_before_margin(self): + for line in self: + if not line.product_id or line.product_id.type == 'service' \ + or line.price_unit <= 0 or line.product_uom_qty <= 0 \ + or not line.vendor_id: + line.item_before_margin = 0 + continue + # calculate margin without tax + sales_price = line.price_reduce_taxexcl * line.product_uom_qty + + purchase_price = line.purchase_price + if line.purchase_tax_id.price_include: + purchase_price = line.purchase_price / 1.11 + + purchase_price = purchase_price * line.product_uom_qty + margin_per_item = sales_price - purchase_price + line.item_before_margin = margin_per_item + @api.onchange('vendor_id') def onchange_vendor_id(self): # TODO : need to change this logic @stephan -- cgit v1.2.3 From e16dd5348125a7fc328052b177635282f931ccd9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 6 May 2025 16:52:44 +0700 Subject: fix logic ask cancel purchasing --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 0d86019e..f99058ea 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1497,7 +1497,7 @@ class SaleOrder(models.Model): def action_cancel(self): # TODO stephan prevent cancel if have invoice, do, and po - if self.state_ask_cancel != 'approve': + if self.state_ask_cancel != 'approve' and self.state not in ['draft', 'sent']: raise UserError("Anda harus approval purchasing terlebih dahulu") main_parent = self.partner_id.get_main_parent() if self._name != 'sale.order': -- cgit v1.2.3 From 1cf66814711b428dd9782292d3403bb9c78b36a2 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 6 May 2025 17:06:09 +0700 Subject: fix when checkout different product category and add tnc --- indoteknik_custom/models/voucher.py | 207 ++++++++++++++++++-------- indoteknik_custom/models/website_user_cart.py | 121 ++++++++------- 2 files changed, 213 insertions(+), 115 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index 7b458d01..baed8062 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -11,43 +11,47 @@ class Voucher(models.Model): name = fields.Char(string='Name') image = fields.Binary(string='Image') code = fields.Char(string='Code', help='Kode voucher yang akan berlaku untuk pengguna') + voucher_category = fields.Many2many('product.public.category', string='Category Voucher', + help='Kategori Produk yang dapat menggunakan voucher ini') description = fields.Text(string='Description') discount_amount = fields.Float(string='Discount Amount') - discount_type = fields.Selection(string='Discount Type', - selection=[ - ('percentage', 'Percentage'), - ('fixed_price', 'Fixed Price'), - ], - help='Select the type of discount:\n' - '- Percentage: Persentase dari total harga.\n' - '- Fixed Price: Jumlah tetap yang dikurangi dari harga total.' - ) - visibility = fields.Selection(string='Visibility', - selection=[ - ('public', 'Public'), - ('private', 'Private') - ], - help='Select the visibility:\n' - '- Public: Ditampilkan kepada seluruh pengguna.\n' - '- Private: Tidak ditampilkan kepada seluruh pengguna.' - ) + discount_type = fields.Selection(string='Discount Type', + selection=[ + ('percentage', 'Percentage'), + ('fixed_price', 'Fixed Price'), + ], + help='Select the type of discount:\n' + '- Percentage: Persentase dari total harga.\n' + '- Fixed Price: Jumlah tetap yang dikurangi dari harga total.' + ) + visibility = fields.Selection(string='Visibility', + selection=[ + ('public', 'Public'), + ('private', 'Private') + ], + help='Select the visibility:\n' + '- Public: Ditampilkan kepada seluruh pengguna.\n' + '- Private: Tidak ditampilkan kepada seluruh pengguna.' + ) start_time = fields.Datetime(string='Start Time') end_time = fields.Datetime(string='End Time') - min_purchase_amount = fields.Integer(string='Min. Purchase Amount', help='Nominal minimum untuk dapat menggunakan voucher. Isi 0 jika tidak ada minimum purchase amount') + min_purchase_amount = fields.Integer(string='Min. Purchase Amount', + help='Nominal minimum untuk dapat menggunakan voucher. Isi 0 jika tidak ada minimum purchase amount') max_discount_amount = fields.Integer(string='Max. Discount Amount', help='Max nominal terhadap persentase diskon') order_ids = fields.One2many('sale.order', 'applied_voucher_id', string='Order') limit = fields.Integer( - string='Limit', + string='Limit', default=0, help='Batas penggunaan voucher keseluruhan. Isi dengan angka 0 untuk penggunaan tanpa batas' ) limit_user = fields.Integer( - string='Limit User', + string='Limit User', default=0, help='Batas penggunaan voucher per pengguna. Misalnya, jika diisi dengan angka 1, maka setiap pengguna hanya dapat menggunakan voucher ini satu kali. Isi dengan angka 0 untuk penggunaan tanpa batas' ) manufacture_ids = fields.Many2many('x_manufactures', string='Brands', help='Voucher appplied only for brand') - excl_pricelist_ids = fields.Many2many('product.pricelist', string='Excluded Pricelists', help='Hide voucher from selected exclude pricelist') + excl_pricelist_ids = fields.Many2many('product.pricelist', string='Excluded Pricelists', + help='Hide voucher from selected exclude pricelist') voucher_line = fields.One2many('voucher.line', 'voucher_id', 'Voucher Line') terms_conditions = fields.Html('Terms and Conditions') apply_type = fields.Selection(string='Apply Type', default="all", selection=[ @@ -64,12 +68,51 @@ class Voucher(models.Model): ('person', "Account Individu"), ('company', "Account Company"), ]) + + def is_voucher_applicable(self, product_id): + if not self.voucher_category: + return True + + public_categories = product_id.public_categ_ids + + return bool(set(public_categories.ids) & set(self.voucher_category.ids)) + + def is_voucher_applicable_for_category(self, category): + import logging + _logger = logging.getLogger(__name__) + + # If voucher has no category restrictions, it applies to all + if not self.voucher_category: + _logger.info("Voucher %s has no category restrictions", self.code) + return True + + # Check if the product's category directly matches one of the voucher's categories + if category.id in self.voucher_category.ids: + _logger.info("Category %s directly matches voucher %s", category.name, self.code) + return True + + # Build the category hierarchy path for the product's category + category_path = [] + current_cat = category + while current_cat: + category_path.append(current_cat.id) + current_cat = current_cat.parent_id + + # Check if any of the voucher's categories are in the category path (parent categories) + for voucher_cat in self.voucher_category: + if voucher_cat.id in category_path: + _logger.info("Voucher category %s is in the category path of %s", voucher_cat.name, category.name) + return True + + _logger.info("No applicable category found for voucher %s and category %s", self.code, category.name) + return False + @api.constrains('description') def _check_description_length(self): for record in self: if record.description and len(record.description) > 120: raise ValidationError('Deskripsi tidak boleh lebih dari 120 karakter') - + @api.constrains('limit', 'limit_user') def _check_limit(self): for rec in self: @@ -87,7 +130,7 @@ class Voucher(models.Model): def res_format(self): datas = [voucher.format() for voucher in self] return datas - + def format(self): ir_attachment = self.env['ir.attachment'] data = { @@ -100,7 +143,7 @@ class Voucher(models.Model): 'remaining_time': self._res_remaining_time(), } return data - + def _res_remaining_time(self): seconds = self._get_remaining_time() remaining_time = timedelta(seconds=seconds) @@ -116,14 +159,31 @@ class Voucher(models.Model): time = minutes unit = 'menit' return f'{time} {unit}' - + def _get_remaining_time(self): calculate_time = self.end_time - datetime.now() return round(calculate_time.total_seconds()) - + def filter_order_line(self, order_line): + import logging + _logger = logging.getLogger(__name__) + voucher_manufacture_ids = self.collect_manufacture_ids() results = [] + + if self.voucher_category and len(order_line) > 0: + for line in order_line: + category_applicable = False + for category in line['product_id'].public_categ_ids: + if self.is_voucher_applicable_for_category(category): + category_applicable = True + break + + if not category_applicable: + _logger.info("Cart contains product %s with non-applicable category - voucher %s cannot be used", + line['product_id'].name, self.code) + return [] + for line in order_line: manufacture_id = line['product_id'].x_manufacture.id or None if self.apply_type == 'brand' and manufacture_id not in voucher_manufacture_ids: @@ -132,35 +192,36 @@ class Voucher(models.Model): product_flashsale = line['product_id']._get_active_flash_sale() if len(product_flashsale) > 0: continue - + results.append(line) - + return results - + def calc_total_order_line(self, order_line): - result = { 'all': 0, 'brand': {} } + result = {'all': 0, 'brand': {}} for line in order_line: manufacture_id = line['product_id'].x_manufacture.id or None manufacture_total = result['brand'].get(manufacture_id, 0) result['brand'][manufacture_id] = manufacture_total + line['subtotal'] result['all'] += line['subtotal'] - + return result - + def calc_discount_amount(self, total): - result = { 'all': 0, 'brand': {} } + result = {'all': 0, 'brand': {}} if self.apply_type in ['all', 'shipping']: if total['all'] < self.min_purchase_amount: return result - + if self.discount_type == 'percentage': decimal_discount = self.discount_amount / 100 discount_all = total['all'] * decimal_discount - result['all'] = min(discount_all, self.max_discount_amount) if self.max_discount_amount > 0 else discount_all + result['all'] = min(discount_all, + self.max_discount_amount) if self.max_discount_amount > 0 else discount_all else: result['all'] = min(self.discount_amount, total['all']) - + return result for line in self.voucher_line: @@ -173,99 +234,115 @@ class Voucher(models.Model): elif line.discount_type == 'percentage': decimal_discount = line.discount_amount / 100 discount_brand = total_brand * decimal_discount - discount_brand = min(discount_brand, line.max_discount_amount) if line.max_discount_amount > 0 else discount_brand + discount_brand = min(discount_brand, + line.max_discount_amount) if line.max_discount_amount > 0 else discount_brand else: discount_brand = min(line.discount_amount, total_brand) - + result['brand'][manufacture_id] = round(discount_brand, 2) result['all'] += discount_brand - + result['all'] = round(result['all'], 2) return result def apply(self, order_line): - order_line = self.filter_order_line(order_line) - amount_total = self.calc_total_order_line(order_line) + + filtered_order_line = self.filter_order_line(order_line) + + amount_total = self.calc_total_order_line(filtered_order_line) + discount = self.calc_discount_amount(amount_total) + return { 'discount': discount, 'total': amount_total, 'type': self.apply_type, - 'valid_order': order_line + 'valid_order': filtered_order_line, } - + def collect_manufacture_ids(self): return [x.manufacture_id.id for x in self.voucher_line] - + def calculate_discount(self, price): if price < self.min_purchase_amount: return 0 - + if self.discount_type == 'fixed_price': return self.discount_amount - + if self.discount_type == 'percentage': discount = price * self.discount_amount / 100 max_disc = self.max_discount_amount return discount if max_disc == 0 else min(discount, max_disc) - + return 0 - + def get_active_voucher(self, domain): current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') domain += [ ('start_time', '<=', current_time), ('end_time', '>=', current_time), ] - vouchers = self.search(domain, order='min_purchase_amount ASC') + vouchers = self.search(domain, order='min_purchase_amount ASC') return vouchers - + def generate_tnc(self): tnc = [] tnc.append('
      ') - tnc.append('
    1. Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher
    2. ') + tnc.append( + '
    3. Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher
    4. ') tnc.append(f'
    5. Voucher berlaku {self._res_remaining_time()} lagi
    6. ') tnc.append(f'
    7. Voucher tidak bisa digunakan apabila terdapat produk flash sale
    8. ') + if self.voucher_category: + category_names = ', '.join([cat.name for cat in self.voucher_category]) + tnc.append( + f'
    9. Voucher hanya berlaku untuk produk dalam kategori {category_names} dan sub-kategorinya
    10. ') + tnc.append( + f'
    11. Voucher tidak dapat digunakan jika ada produk di keranjang yang tidak termasuk dalam kategori tersebut
    12. ') + if len(self.voucher_line) > 0: - brand_names = ', '.join([x.manufacture_id.x_name or '' for x in self.voucher_line]) - tnc.append(f'
    13. Voucher berlaku untuk produk dari brand {brand_names}
    14. ') + tnc.append(f'
    15. Voucher berlaku untuk produk dari brand terpilih
    16. ') tnc.append(self.generate_detail_tnc()) tnc.append('
    ') - + return ' '.join(tnc) - + def generate_detail_tnc(self): def format_currency(amount): formatted_number = '{:,.0f}'.format(amount).replace(',', '.') return f'Rp{formatted_number}' - + tnc = [] if self.apply_type == 'all': tnc.append('
  • ') tnc.append('Nominal potongan yang bisa didapatkan sebesar') - tnc.append(f'{self.discount_amount}%' if self.discount_type == 'percentage' else format_currency(self.discount_amount)) - + tnc.append(f'{self.discount_amount}%' if self.discount_type == 'percentage' else format_currency( + self.discount_amount)) + if self.discount_type == 'percentage' and self.max_discount_amount > 0: tnc.append(f'hingga {format_currency(self.max_discount_amount)}') - tnc.append(f'dengan minimum pembelian {format_currency(self.min_purchase_amount)}' if self.min_purchase_amount > 0 else 'tanpa minimum pembelian') + tnc.append( + f'dengan minimum pembelian {format_currency(self.min_purchase_amount)}' if self.min_purchase_amount > 0 else 'tanpa minimum pembelian') tnc.append('
  • ') else: for line in self.voucher_line: line_tnc = [] line_tnc.append(f'Nominal potongan produk {line.manufacture_id.x_name} yang bisa didapatkan sebesar') - line_tnc.append(f'{line.discount_amount}%' if line.discount_type == 'percentage' else format_currency(line.discount_amount)) + line_tnc.append(f'{line.discount_amount}%' if line.discount_type == 'percentage' else format_currency( + line.discount_amount)) if line.discount_type == 'percentage' and line.max_discount_amount > 0: line_tnc.append(f'hingga {format_currency(line.max_discount_amount)}') - - line_tnc.append(f'dengan minimum pembelian {format_currency(line.min_purchase_amount)}' if line.min_purchase_amount > 0 else 'tanpa minimum pembelian') + + line_tnc.append( + f'dengan minimum pembelian {format_currency(line.min_purchase_amount)}' if line.min_purchase_amount > 0 else 'tanpa minimum pembelian') line_tnc = ' '.join(line_tnc) tnc.append(f'
  • {line_tnc}
  • ') return ' '.join(tnc) - # copy semua data kalau diduplicate + # copy semua data kalau diduplicate def copy(self, default=None): default = dict(default or {}) voucher_lines = [] @@ -280,4 +357,4 @@ class Voucher(models.Model): })) default['voucher_line'] = voucher_lines - return super(Voucher, self).copy(default) \ No newline at end of file + return super(Voucher, self).copy(default) diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py index 44393cf1..a6d08949 100644 --- a/indoteknik_custom/models/website_user_cart.py +++ b/indoteknik_custom/models/website_user_cart.py @@ -1,10 +1,11 @@ from odoo import fields, models, api from datetime import datetime, timedelta + class WebsiteUserCart(models.Model): _name = 'website.user.cart' _rec_name = 'user_id' - + user_id = fields.Many2one('res.users', string='User') product_id = fields.Many2one('product.product', string='Product') program_line_id = fields.Many2one('promotion.program.line', string='Program', help="Apply program") @@ -18,7 +19,8 @@ class WebsiteUserCart(models.Model): is_reminder = fields.Boolean(string='Reminder?') phone_user = fields.Char(string='Phone', related='user_id.mobile') price = fields.Float(string='Price', compute='_compute_price') - program_product_id = fields.Many2one('product.product', string='Program Products', compute='_compute_program_product_ids') + program_product_id = fields.Many2one('product.product', string='Program Products', + compute='_compute_program_product_ids') @api.depends('program_line_id') def _compute_program_product_ids(self): @@ -55,6 +57,12 @@ class WebsiteUserCart(models.Model): product = self.product_id.v2_api_single_response(self.product_id) res.update(product) + # Add category information + res['categories'] = [{ + 'id': cat.id, + 'name': cat.name + } for cat in self.product_id.public_categ_ids] + # Check if the product's inventory location is in ID 57 or 83 target_locations = [57, 83] stock_quant = self.env['stock.quant'].search([ @@ -90,7 +98,14 @@ class WebsiteUserCart(models.Model): def get_products(self): products = [x.get_product() for x in self] - + + for i, cart_item in enumerate(self): + if cart_item.product_id and i < len(products): + products[i]['categories'] = [{ + 'id': cat.id, + 'name': cat.name + } for cat in cart_item.product_id.public_categ_ids] + return products def get_product_by_user(self, user_id, selected=False, source=False): @@ -121,10 +136,10 @@ class WebsiteUserCart(models.Model): products = products_active.get_products() return products - + def get_user_checkout(self, user_id, voucher=False, voucher_shipping=False, source=False): products = self.get_product_by_user(user_id=user_id, selected=True, source=source) - + total_purchase = 0 total_discount = 0 for product in products: @@ -132,9 +147,9 @@ class WebsiteUserCart(models.Model): price = product['package_price'] * product['quantity'] else: price = product['price']['price'] * product['quantity'] - + discount_price = price - product['price']['price_discount'] * product['quantity'] - + total_purchase += price total_discount += discount_price @@ -142,7 +157,7 @@ class WebsiteUserCart(models.Model): discount_voucher = 0 discount_voucher_shipping = 0 order_line = [] - + if voucher or voucher_shipping: for product in products: if product['cart_type'] == 'promotion': continue @@ -153,16 +168,16 @@ class WebsiteUserCart(models.Model): 'qty': product['quantity'], 'subtotal': product['subtotal'] }) - + if voucher: voucher_info = voucher.apply(order_line) discount_voucher = voucher_info['discount']['all'] subtotal -= discount_voucher - + if voucher_shipping: voucher_shipping_info = voucher_shipping.apply(order_line) - discount_voucher_shipping = voucher_shipping_info['discount']['all'] - + discount_voucher_shipping = voucher_shipping_info['discount']['all'] + tax = round(subtotal * 0.11) grand_total = subtotal + tax total_weight = sum(x['weight'] * x['quantity'] for x in products) @@ -179,28 +194,31 @@ class WebsiteUserCart(models.Model): 'kg': total_weight, 'g': total_weight * 1000 }, - 'has_product_without_weight': any(not product.get('weight') or product.get('weight') == 0 for product in products), + 'has_product_without_weight': any( + not product.get('weight') or product.get('weight') == 0 for product in products), 'products': products } return result - + def action_mail_reminder_to_checkout(self, limit=200): user_ids = self.search([('is_reminder', '=', False)]).mapped('user_id')[:limit] - + for user in user_ids: latest_cart = self.search([('user_id', '=', user.id)], order='create_date desc', limit=1) - + carts_to_remind = self.search([('user_id', '=', user.id)]) - + if latest_cart and not latest_cart.is_reminder: for cart in carts_to_remind: check = cart.check_product_flashsale(cart.product_id.id) - if not cart.program_line_id and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code and check['is_flashsale'] == False: + if not cart.program_line_id and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code and \ + check['is_flashsale'] == False: cart.is_selected = True - if cart.program_line_id or check['is_flashsale'] or cart.product_id.default_code and 'BOM' in cart.product_id.default_code: + if cart.program_line_id or check[ + 'is_flashsale'] or cart.product_id.default_code and 'BOM' in cart.product_id.default_code: cart.is_selected = False cart.is_reminder = True - + template = self.env.ref('indoteknik_custom.mail_template_user_cart_reminder_to_checkout') template.send_mail(latest_cart.id, force_send=True) @@ -234,8 +252,9 @@ class WebsiteUserCart(models.Model): break product_discount = subtotal_promo if cart.program_line_id or check['is_flashsale'] else subtotal - total_discount += product_discount - if check['is_flashsale'] == False and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code: + total_discount += product_discount + if check[ + 'is_flashsale'] == False and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code: voucher_product = subtotal * (discount_amount / 100.0) total_voucher += voucher_product @@ -253,14 +272,15 @@ class WebsiteUserCart(models.Model): def check_product_flashsale(self, product_id): product = product_id current_time = datetime.utcnow() - found_product = self.env['product.pricelist.item'].search([('product_id', '=', product_id), ('pricelist_id.is_flash_sale', '=', True)]) + found_product = self.env['product.pricelist.item'].search( + [('product_id', '=', product_id), ('pricelist_id.is_flash_sale', '=', True)]) if found_product: for found in found_product: pricelist_flashsale = found.pricelist_id if pricelist_flashsale.start_date <= current_time <= pricelist_flashsale.end_date: - return { + return { 'is_flashsale': True, 'price': found.fixed_price } @@ -269,10 +289,9 @@ class WebsiteUserCart(models.Model): 'is_flashsale': False } - return { + return { 'is_flashsale': False } - # if found_product: # start_date = found_product.pricelist_id.start_date @@ -291,26 +310,26 @@ class WebsiteUserCart(models.Model): # return { # 'is_flashsale': False # } - + def get_data_promo(self, program_line_id): program_line_product = self.env['promotion.product'].search([ ('program_line_id', '=', program_line_id) - ]) - + ]) + program_free_product = self.env['promotion.free_product'].search([ ('program_line_id', '=', program_line_id) - ]) + ]) return program_line_product, program_free_product - + def get_weight_product(self, program_line_id): program_line_product = self.env['promotion.product'].search([ ('program_line_id', '=', program_line_id) - ]) - + ]) + program_free_product = self.env['promotion.free_product'].search([ ('program_line_id', '=', program_line_id) - ]) - + ]) + real_weight = 0.0 if program_line_product: for product in program_line_product: @@ -321,16 +340,16 @@ class WebsiteUserCart(models.Model): real_weight += product.product_id.weight return real_weight - + def get_price_coret(self, program_line_id): program_line_product = self.env['promotion.product'].search([ ('program_line_id', '=', program_line_id) - ]) - + ]) + program_free_product = self.env['promotion.free_product'].search([ ('program_line_id', '=', program_line_id) - ]) - + ]) + price_coret = 0.0 for product in program_line_product: price = self.get_price_website(product.product_id.id) @@ -340,20 +359,22 @@ class WebsiteUserCart(models.Model): price = self.get_price_website(product.product_id.id) price_coret += price['price'] * product.qty - return price_coret - + return price_coret + def get_price_website(self, product_id): - price_website = self.env['product.pricelist.item'].search([('product_id', '=', product_id), ('pricelist_id', '=', 17022)], limit=1) - - price_tier = self.env['product.pricelist.item'].search([('product_id', '=', product_id), ('pricelist_id', '=', 17023)], limit=1) - + price_website = self.env['product.pricelist.item'].search( + [('product_id', '=', product_id), ('pricelist_id', '=', 17022)], limit=1) + + price_tier = self.env['product.pricelist.item'].search( + [('product_id', '=', product_id), ('pricelist_id', '=', 17023)], limit=1) + fixed_price = price_website.fixed_price if price_website else 0.0 discount = price_tier.price_discount if price_tier else 0.0 - + discounted_price = fixed_price - (fixed_price * discount / 100) - + final_price = discounted_price / 1.11 - + return { 'price': final_price, 'web_price': discounted_price @@ -365,4 +386,4 @@ class WebsiteUserCart(models.Model): def format_currency(self, number): number = int(number) - return "{:,}".format(number).replace(',', '.') \ No newline at end of file + return "{:,}".format(number).replace(',', '.') -- cgit v1.2.3 From 0c9f3297bfda30ec813833a5db3bcbf7a24f327d Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 7 May 2025 06:49:50 +0700 Subject: simplify tnc --- indoteknik_custom/models/voucher.py | 67 +++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 26 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index baed8062..0b47e5f0 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -309,39 +309,54 @@ class Voucher(models.Model): return ' '.join(tnc) def generate_detail_tnc(self): - def format_currency(amount): - formatted_number = '{:,.0f}'.format(amount).replace(',', '.') - return f'Rp{formatted_number}' + # def format_currency(amount): + # formatted_number = '{:,.0f}'.format(amount).replace(',', '.') + # return f'Rp{formatted_number}' tnc = [] if self.apply_type == 'all': tnc.append('
  • ') - tnc.append('Nominal potongan yang bisa didapatkan sebesar') - tnc.append(f'{self.discount_amount}%' if self.discount_type == 'percentage' else format_currency( - self.discount_amount)) - - if self.discount_type == 'percentage' and self.max_discount_amount > 0: - tnc.append(f'hingga {format_currency(self.max_discount_amount)}') - - tnc.append( - f'dengan minimum pembelian {format_currency(self.min_purchase_amount)}' if self.min_purchase_amount > 0 else 'tanpa minimum pembelian') + tnc.append('Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.') tnc.append('
  • ') - else: - for line in self.voucher_line: - line_tnc = [] - line_tnc.append(f'Nominal potongan produk {line.manufacture_id.x_name} yang bisa didapatkan sebesar') - line_tnc.append(f'{line.discount_amount}%' if line.discount_type == 'percentage' else format_currency( - line.discount_amount)) - - if line.discount_type == 'percentage' and line.max_discount_amount > 0: - line_tnc.append(f'hingga {format_currency(line.max_discount_amount)}') - - line_tnc.append( - f'dengan minimum pembelian {format_currency(line.min_purchase_amount)}' if line.min_purchase_amount > 0 else 'tanpa minimum pembelian') - line_tnc = ' '.join(line_tnc) - tnc.append(f'
  • {line_tnc}
  • ') + elif len(self.voucher_line) > 0: + tnc.append( + '
  • Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.
  • ') return ' '.join(tnc) + # def generate_detail_tnc(self): + # def format_currency(amount): + # formatted_number = '{:,.0f}'.format(amount).replace(',', '.') + # return f'Rp{formatted_number}' + # + # tnc = [] + # if self.apply_type == 'all': + # tnc.append('
  • ') + # tnc.append('Nominal potongan yang bisa didapatkan sebesar') + # tnc.append(f'{self.discount_amount}%' if self.discount_type == 'percentage' else format_currency( + # self.discount_amount)) + # + # if self.discount_type == 'percentage' and self.max_discount_amount > 0: + # tnc.append(f'hingga {format_currency(self.max_discount_amount)}') + # + # tnc.append( + # f'dengan minimum pembelian {format_currency(self.min_purchase_amount)}' if self.min_purchase_amount > 0 else 'tanpa minimum pembelian') + # tnc.append('
  • ') + # else: + # for line in self.voucher_line: + # line_tnc = [] + # line_tnc.append(f'Nominal potongan produk {line.manufacture_id.x_name} yang bisa didapatkan sebesar') + # line_tnc.append(f'{line.discount_amount}%' if line.discount_type == 'percentage' else format_currency( + # line.discount_amount)) + # + # if line.discount_type == 'percentage' and line.max_discount_amount > 0: + # line_tnc.append(f'hingga {format_currency(line.max_discount_amount)}') + # + # line_tnc.append( + # f'dengan minimum pembelian {format_currency(line.min_purchase_amount)}' if line.min_purchase_amount > 0 else 'tanpa minimum pembelian') + # line_tnc = ' '.join(line_tnc) + # tnc.append(f'
  • {line_tnc}
  • ') + # return ' '.join(tnc) + # copy semua data kalau diduplicate def copy(self, default=None): default = dict(default or {}) -- cgit v1.2.3 From 1775e7907c905e85f40cecfef69c651c45e64de3 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 7 May 2025 09:18:32 +0700 Subject: remove logging --- indoteknik_custom/models/voucher.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index 0b47e5f0..cda9309f 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -165,8 +165,8 @@ class Voucher(models.Model): return round(calculate_time.total_seconds()) def filter_order_line(self, order_line): - import logging - _logger = logging.getLogger(__name__) + # import logging + # _logger = logging.getLogger(__name__) voucher_manufacture_ids = self.collect_manufacture_ids() results = [] @@ -180,8 +180,8 @@ class Voucher(models.Model): break if not category_applicable: - _logger.info("Cart contains product %s with non-applicable category - voucher %s cannot be used", - line['product_id'].name, self.code) + # _logger.info("Cart contains product %s with non-applicable category - voucher %s cannot be used", + # line['product_id'].name, self.code) return [] for line in order_line: -- cgit v1.2.3 From ab1c5cabe7bdcfb950a2d7201ce367eea49ad7f2 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 7 May 2025 09:59:45 +0700 Subject: fix date time utc --- indoteknik_custom/models/commision.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 32e81b9a..bd4c06a4 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -1,8 +1,10 @@ from odoo import models, api, fields, _ from odoo.exceptions import UserError from datetime import datetime +# import datetime import logging from terbilang import Terbilang +import pytz _logger = logging.getLogger(__name__) @@ -301,30 +303,34 @@ class CustomerCommision(models.Model): return result def action_confirm_customer_commision(self): - now = datetime.utcnow() + jakarta_tz = pytz.timezone('Asia/Jakarta') + now = datetime.now(jakarta_tz) + + now_naive = now.replace(tzinfo=None) + if not self.status or self.status == 'draft': self.status = 'pengajuan1' elif self.status == 'pengajuan1' and self.env.user.is_sales_manager: self.status = 'pengajuan2' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name - self.date_approved_sales = now + self.date_approved_sales = now_naive self.position_sales = 'Sales Manager' elif self.status == 'pengajuan2' and self.env.user.id == 19: self.status = 'pengajuan3' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name - self.date_approved_marketing = now + self.date_approved_marketing = now_naive self.position_marketing = 'Marketing Manager' elif self.status == 'pengajuan3' and self.env.user.is_leader: self.status = 'pengajuan4' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name - self.date_approved_pimpinan = now + self.date_approved_pimpinan = now_naive self.position_pimpinan = 'Pimpinan' elif self.status == 'pengajuan4' and self.env.user.id == 1272: for line in self.commision_lines: line.invoice_id.is_customer_commision = True self.status = 'approved' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name - self.date_approved_accounting = now + self.date_approved_accounting = now_naive self.position_accounting = 'Accounting' else: raise UserError('Harus di approved oleh yang bersangkutan') -- cgit v1.2.3 From a9a6d2b0bfdce88ebc42ac92fcbc016e30e8ff72 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Wed, 7 May 2025 13:39:57 +0700 Subject: bug fix error matches so in purchase order --- indoteknik_custom/models/automatic_purchase.py | 52 +++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index fbdf8dae..a39abba9 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -1,4 +1,4 @@ -from odoo import models, api, fields +from odoo import models, api, fields, tools from odoo.exceptions import UserError from datetime import datetime import logging, math @@ -284,7 +284,7 @@ class AutomaticPurchase(models.Model): def create_purchase_order_sales_match(self, purchase_order): matches_so_product_ids = [line.product_id.id for line in purchase_order.order_line] - matches_so = self.env['automatic.purchase.sales.match'].search([ + matches_so = self.env['v.sale.notin.matchpo'].search([ ('automatic_purchase_id', '=', self.id), ('sale_line_id.product_id', 'in', matches_so_product_ids), ]) @@ -292,6 +292,8 @@ class AutomaticPurchase(models.Model): sale_ids_set = set() sale_ids_name = set() for sale_order in matches_so: + # @stephan skip so line yang sudah pernah ada di purchase order sales match sebelumnya + salesperson_name = sale_order.sale_id.user_id.name sale_id_with_salesperson = f"{sale_order.sale_id.name} - {salesperson_name}" @@ -655,3 +657,49 @@ class SyncPurchasingJob(models.Model): outgoing = fields.Float(string="Outgoing") action = fields.Char(string="Status") date = fields.Datetime(string="Date Sync") + + +class SaleNotInMatchPO(models.Model): + # created by @stephan for speed up performance while create po from automatic purchase + _name = 'v.sale.notin.matchpo' + _auto = False + _rec_name = 'id' + + id = fields.Integer() + automatic_purchase_id = fields.Many2one('automatic.purchase', string='APO') + automatic_purchase_line_id = fields.Many2one('automatic.purchase.line', string='APO Line') + sale_id = fields.Many2one('sale.order', string='Sale') + sale_line_id = fields.Many2one('sale.order.line', string='Sale Line') + picking_id = fields.Many2one('stock.picking', string='Picking') + move_id = fields.Many2one('stock.move', string='Move') + partner_id = fields.Many2one('res.partner', string='Partner') + partner_invoice_id = fields.Many2one('res.partner', string='Partner Invoice') + salesperson_id = fields.Many2one('res.user', string='Salesperson') + product_id = fields.Many2one('product.product', string='Product') + qty_so = fields.Float(string='Qty SO') + qty_po = fields.Float(string='Qty PO') + create_uid = fields.Many2one('res.user', string='Created By') + create_date = fields.Datetime(string='Create Date') + write_uid = fields.Many2one('res.user', string='Updated By') + write_date = fields.Many2one(string='Updated') + purchase_price = fields.Many2one(string='Purchase Price') + purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax') + note_procurement = fields.Many2one(string='Note Procurement') + + def init(self): + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute(""" + CREATE OR REPLACE VIEW %s AS( + select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id, + apsm.picking_id, apsm.move_id, apsm.partner_id, + apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid, + apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price, + apsm.purchase_tax_id, apsm.note_procurement + from automatic_purchase_sales_match apsm + where apsm.sale_line_id not in ( + select distinct coalesce(posm.sale_line_id,0) + from purchase_order_sales_match posm + where posm.state not in ('cancel') + ) + ) + """ % self._table) -- cgit v1.2.3 From 5d54ea0ad8d3c3a5dc125507122c395ac27a5729 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 7 May 2025 13:42:00 +0700 Subject: push --- indoteknik_custom/models/account_move.py | 13 +++++++++++++ indoteknik_custom/models/sale_order.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 906985de..30de67be 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -66,6 +66,19 @@ class AccountMove(models.Model): other_taxes = fields.Float(string="Other Taxes", compute='compute_other_taxes') is_hr = fields.Boolean(string="Is HR?", default=False) purchase_order_id = fields.Many2one('purchase.order', string='Purchase Order') + length_of_payment = fields.Integer(string="Length of Payment", compute='compute_length_of_payment') + + def compute_length_of_payment(self): + for rec in self: + payment_term = rec.invoice_payment_term_id.line_ids[0].days + terima_faktur = rec.date_terima_tukar_faktur + payment = self.search([('ref', '=', rec.name), ('move_type', '=', 'entry')], limit=1) + + if payment and terima_faktur: + date_diff = terima_faktur - payment.date + rec.length_of_payment = date_diff.days + payment_term + else: + rec.length_of_payment = 0 def _update_line_name_from_ref(self): """Update all account.move.line name fields with ref from account.move""" diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index f99058ea..0d4fc6c3 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -297,7 +297,7 @@ class SaleOrder(models.Model): ) nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") - hold_outgoing = fields.Boolean('Hold Outgoing SO') + hold_outgoing = fields.Boolean('Hold Outgoing SO', tracking=3) state_ask_cancel = fields.Selection([ ('hold', 'Hold'), ('approve', 'Approve') -- cgit v1.2.3 From 71a780abc391d11c2fe2ea16a953eefd2ff74219 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 7 May 2025 13:47:44 +0700 Subject: fix posm query --- indoteknik_custom/models/automatic_purchase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index a39abba9..d619e160 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -699,7 +699,8 @@ class SaleNotInMatchPO(models.Model): where apsm.sale_line_id not in ( select distinct coalesce(posm.sale_line_id,0) from purchase_order_sales_match posm - where posm.state not in ('cancel') + left join purchase_order po on po.id = posm.purchase_order_id + where po.state not in ('cancel') ) ) """ % self._table) -- cgit v1.2.3 From 59079cd2ff5ff82f444566a6d1b62fafcd4c6c44 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 7 May 2025 15:00:08 +0700 Subject: add more tnc --- indoteknik_custom/models/voucher.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index cda9309f..145cd814 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -309,15 +309,24 @@ class Voucher(models.Model): return ' '.join(tnc) def generate_detail_tnc(self): - # def format_currency(amount): - # formatted_number = '{:,.0f}'.format(amount).replace(',', '.') - # return f'Rp{formatted_number}' + def format_currency(amount): + formatted_number = '{:,.0f}'.format(amount).replace(',', '.') + return f'Rp{formatted_number}' tnc = [] + if self.apply_type == 'all': tnc.append('
  • ') tnc.append('Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.') tnc.append('
  • ') + tnc.append('
  • ') + if self.discount_type == 'fixed_price': + tnc.append( + f'Voucher untuk minimal pembelian {format_currency(self.min_purchase_amount)} dengan potongan hingga {format_currency(self.discount_amount)}') + tnc.append('
  • ') + elif self.discount_type == 'percentage': + tnc.append( + f'Voucher untuk minimal pembelian {format_currency(self.min_purchase_amount)} dengan potongan hingga {self.discount_amount}%') elif len(self.voucher_line) > 0: tnc.append( '
  • Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.
  • ') -- cgit v1.2.3 From 34c21a73b535e8c543deb281d2bf587bfd79afb5 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Wed, 7 May 2025 15:13:51 +0700 Subject: change to join in purchase sales order match --- indoteknik_custom/models/automatic_purchase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index d619e160..1a1b3a30 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -699,7 +699,7 @@ class SaleNotInMatchPO(models.Model): where apsm.sale_line_id not in ( select distinct coalesce(posm.sale_line_id,0) from purchase_order_sales_match posm - left join purchase_order po on po.id = posm.purchase_order_id + join purchase_order po on po.id = posm.purchase_order_id where po.state not in ('cancel') ) ) -- cgit v1.2.3 From 7b811b7c693d92da0dfe359df8c7caad59194db5 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 7 May 2025 16:13:06 +0700 Subject: remove logging and update tnc --- indoteknik_custom/models/voucher.py | 42 +++++++++---------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index 145cd814..66e336c9 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -78,33 +78,22 @@ class Voucher(models.Model): return bool(set(public_categories.ids) & set(self.voucher_category.ids)) def is_voucher_applicable_for_category(self, category): - import logging - _logger = logging.getLogger(__name__) - - # If voucher has no category restrictions, it applies to all if not self.voucher_category: - _logger.info("Voucher %s has no category restrictions", self.code) return True - # Check if the product's category directly matches one of the voucher's categories if category.id in self.voucher_category.ids: - _logger.info("Category %s directly matches voucher %s", category.name, self.code) return True - # Build the category hierarchy path for the product's category category_path = [] current_cat = category while current_cat: category_path.append(current_cat.id) current_cat = current_cat.parent_id - # Check if any of the voucher's categories are in the category path (parent categories) for voucher_cat in self.voucher_category: if voucher_cat.id in category_path: - _logger.info("Voucher category %s is in the category path of %s", voucher_cat.name, category.name) return True - _logger.info("No applicable category found for voucher %s and category %s", self.code, category.name) return False @api.constrains('description') @@ -294,15 +283,9 @@ class Voucher(models.Model): '
  • Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher
  • ') tnc.append(f'
  • Voucher berlaku {self._res_remaining_time()} lagi
  • ') tnc.append(f'
  • Voucher tidak bisa digunakan apabila terdapat produk flash sale
  • ') - if self.voucher_category: - category_names = ', '.join([cat.name for cat in self.voucher_category]) - tnc.append( - f'
  • Voucher hanya berlaku untuk produk dalam kategori {category_names} dan sub-kategorinya
  • ') - tnc.append( - f'
  • Voucher tidak dapat digunakan jika ada produk di keranjang yang tidak termasuk dalam kategori tersebut
  • ') - - if len(self.voucher_line) > 0: - tnc.append(f'
  • Voucher berlaku untuk produk dari brand terpilih
  • ') + tnc.append( + '
  • Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.
  • ') + tnc.append(self.generate_detail_tnc()) tnc.append('') @@ -316,20 +299,15 @@ class Voucher(models.Model): tnc = [] if self.apply_type == 'all': - tnc.append('
  • ') - tnc.append('Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.') - tnc.append('
  • ') - tnc.append('
  • ') - if self.discount_type == 'fixed_price': + if self.voucher_category: + category_names = ', '.join([cat.name for cat in self.voucher_category]) tnc.append( - f'Voucher untuk minimal pembelian {format_currency(self.min_purchase_amount)} dengan potongan hingga {format_currency(self.discount_amount)}') - tnc.append('
  • ') - elif self.discount_type == 'percentage': + f'
  • Voucher hanya berlaku untuk produk dalam kategori {category_names} dan sub-kategorinya
  • ') tnc.append( - f'Voucher untuk minimal pembelian {format_currency(self.min_purchase_amount)} dengan potongan hingga {self.discount_amount}%') - elif len(self.voucher_line) > 0: - tnc.append( - '
  • Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.
  • ') + f'
  • Voucher tidak dapat digunakan jika ada produk di keranjang yang tidak termasuk dalam kategori tersebut
  • ') + else: + tnc.append(f'
  • Voucher berlaku untuk produk dari brand terpilih
  • ') + return ' '.join(tnc) # def generate_detail_tnc(self): -- cgit v1.2.3 From 8c8cc93b60dfef60342ccd47b440f702bc18c754 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 7 May 2025 16:20:15 +0700 Subject: change trigger unlink note pj and add validation fill sj return date --- indoteknik_custom/models/automatic_purchase.py | 10 ++++++++++ indoteknik_custom/models/purchasing_job_multi_update.py | 2 +- indoteknik_custom/models/stock_picking.py | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index d619e160..ddb4a973 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -67,6 +67,15 @@ class AutomaticPurchase(models.Model): if count > 0: raise UserError('Ada sekitar %s SO Yang sudah create PO, berikut SO nya: %s' % (count, ', '.join(names))) + + def unlink_note_pj(self): + product = self.purchase_lines.mapped('product_id') + pj_state = self.env['purchasing.job.state'].search([ + ('purchasing_job_id', 'in', product.ids) + ]) + + for line in pj_state: + line.unlink() def create_po_from_automatic_purchase(self): if not self.purchase_lines: @@ -75,6 +84,7 @@ class AutomaticPurchase(models.Model): raise UserError('Sudah pernah di create PO') current_time = datetime.now() + self.unlink_note_pj() vendor_ids = self.env['automatic.purchase.line'].read_group( [('automatic_purchase_id', '=', self.id), ('partner_id', '!=', False)], fields=['partner_id'], diff --git a/indoteknik_custom/models/purchasing_job_multi_update.py b/indoteknik_custom/models/purchasing_job_multi_update.py index deba960a..80a43e45 100644 --- a/indoteknik_custom/models/purchasing_job_multi_update.py +++ b/indoteknik_custom/models/purchasing_job_multi_update.py @@ -18,7 +18,7 @@ class PurchasingJobMultiUpdate(models.TransientModel): ('purchasing_job_id', '=', product.id) ]) - purchasing_job_state.unlink() + # purchasing_job_state.unlink() purchasing_job_state.create({ 'purchasing_job_id': product.id, diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f431d817..d032f99f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -276,6 +276,15 @@ class StockPicking(models.Model): last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') + @api.constrains('sj_return_date') + def _check_sj_return_date(self): + for record in self: + if not record.driver_arrival_date: + if record.sj_return_date: + raise ValidationError( + _("Anda tidak dapat mengubah Tanggal Pengembalian setelah Tanggal Pengiriman!") + ) + def _check_date_doc_kirim_modification(self): for record in self: if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'): -- cgit v1.2.3 From 8923005630ca26e9863f71c031a28183eb590727 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 7 May 2025 16:55:43 +0700 Subject: (andri) setiap melakukan edit product, history perubahan akan tercatat di log note --- indoteknik_custom/models/product_template.py | 75 ++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index a09570f4..8e475f95 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -388,12 +388,77 @@ class ProductTemplate(models.Model): self.env['token.storage'].create([values]) return values + # simpan data lama + def _collect_old_values(self, vals): + return { + record.id: { + field_name: record[field_name] + for field_name in vals.keys() + if field_name in record._fields + } + for record in self + } + + # log perubahan field + def _log_field_changes(self, vals, old_values): + exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] + + for record in self: + changes = [] + for field_name in vals: + if field_name not in record._fields or field_name in exclude_fields: + continue + + field = record._fields[field_name] + field_label = field.string or field_name + old_value = old_values.get(record.id, {}).get(field_name) + new_value = record[field_name] # nilai setelah write + + def stringify(val): + if val in [None, False]: + return 'None' + if isinstance(field, fields.Selection): + return dict(field.selection).get(val, str(val)) + if isinstance(field, fields.Boolean): + return 'Yes' if val else 'No' + if isinstance(field, fields.Many2one): + return val.name if hasattr(val, 'name') else str(val) + if isinstance(field, fields.Many2many): + records = val if isinstance(val, models.Model) else val + names = [] + if records: + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + names = records.mapped(attr) + break + if not names: + names = [str(r.id) for r in records] + return ", ".join(names) if names else 'None' + return str(val) + + old_val_str = stringify(old_value) + new_val_str = stringify(new_value) + + if old_val_str != new_val_str: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + + if changes: + message = "Updated:
      %s
    " % "".join(changes) + record.message_post(body=message) + + # simpan data lama dan log perubahan field def write(self, vals): - # for rec in self: - # if rec.id == 224484: - # raise UserError('Tidak dapat mengubah produk sementara') - - return super(ProductTemplate, self).write(vals) + old_values = self._collect_old_values(vals) + result = super().write(vals) + self._log_field_changes(vals, old_values) + return result + + # def write(self, vals): + # # for rec in self: + # # if rec.id == 224484: + # # raise UserError('Tidak dapat mengubah produk sementara') + # self._log_field_changes(vals) + # return super(ProductTemplate, self).write(vals) class ProductProduct(models.Model): _inherit = "product.product" -- cgit v1.2.3 From a045b3dc711fb049a78db0e65088a561e0fd9c4a Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 8 May 2025 07:11:23 +0700 Subject: update tnc --- indoteknik_custom/models/voucher.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index 66e336c9..91d87ddd 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -276,6 +276,10 @@ class Voucher(models.Model): return vouchers def generate_tnc(self): + def format_currency(amount): + formatted_number = '{:,.0f}'.format(amount).replace(',', '.') + return f'Rp{formatted_number}' + tnc = [] tnc.append('
      ') @@ -283,31 +287,28 @@ class Voucher(models.Model): '
    1. Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher
    2. ') tnc.append(f'
    3. Voucher berlaku {self._res_remaining_time()} lagi
    4. ') tnc.append(f'
    5. Voucher tidak bisa digunakan apabila terdapat produk flash sale
    6. ') - tnc.append( - '
    7. Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.
    8. ') - - tnc.append(self.generate_detail_tnc()) - tnc.append('
    ') - - return ' '.join(tnc) - - def generate_detail_tnc(self): - def format_currency(amount): - formatted_number = '{:,.0f}'.format(amount).replace(',', '.') - return f'Rp{formatted_number}' - - tnc = [] - if self.apply_type == 'all': + if self.apply_type == 'brand': + tnc.append(f'
  • Voucher berlaku untuk produk dari brand terpilih
  • ') + tnc.append( + f'
  • Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.
  • ') + elif self.apply_type == 'all': if self.voucher_category: category_names = ', '.join([cat.name for cat in self.voucher_category]) tnc.append( f'
  • Voucher hanya berlaku untuk produk dalam kategori {category_names} dan sub-kategorinya
  • ') tnc.append( f'
  • Voucher tidak dapat digunakan jika ada produk di keranjang yang tidak termasuk dalam kategori tersebut
  • ') - else: - tnc.append(f'
  • Voucher berlaku untuk produk dari brand terpilih
  • ') + if self.discount_type == 'percentage' and self.apply_type != 'brand': + tnc.append( + f'
  • Nominal potongan produk yang bisa didapatkan sebesar {self.max_discount_amount}% dengan minimum pembelian {self.min_purchase_amount}
  • ') + elif self.discount_type == 'percentage' and self.apply_type != 'brand': + tnc.append( + f'
  • Nominal potongan produk yang bisa didapatkan sebesar {format_currency(self.discount_amount)} dengan minimum pembelian {format_currency(self.min_purchase_amount)}
  • ') + + tnc.append('') + # tnc.append(self.generate_detail_tnc()) return ' '.join(tnc) # def generate_detail_tnc(self): -- cgit v1.2.3 From 5f04912a511578a73fe298f154713ade56e933b4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 8 May 2025 08:14:34 +0700 Subject: update tnc for shipping --- indoteknik_custom/models/voucher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index 91d87ddd..b213a039 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -292,7 +292,7 @@ class Voucher(models.Model): tnc.append(f'
  • Voucher berlaku untuk produk dari brand terpilih
  • ') tnc.append( f'
  • Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.
  • ') - elif self.apply_type == 'all': + elif self.apply_type == 'all' or self.apply_type == 'shipping': if self.voucher_category: category_names = ', '.join([cat.name for cat in self.voucher_category]) tnc.append( -- cgit v1.2.3 From 6b3401c7542e26830d60667fa0dbd78c25cc7be9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 8 May 2025 09:39:29 +0700 Subject: push --- indoteknik_custom/models/commision.py | 3 +++ indoteknik_custom/models/stock_picking.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index bd4c06a4..788fc0f9 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -278,6 +278,9 @@ class CustomerCommision(models.Model): @api.constrains('commision_amt') def _onchange_commision_amt(self): + """ + Constrain to update commision percent from commision amount + """ if not self.env.context.get('_onchange_commision_amt', True): return diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d032f99f..ccccbcdc 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1248,7 +1248,7 @@ class StockPicking(models.Model): continue invoice = self.env['account.move'].search( - [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel'])], limit=1) + [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel']), ('move_type', '=', 'out_invoice')], limit=1) if not invoice: continue -- cgit v1.2.3 From 75bc2e5226862b8401424942ba464f6e70a03604 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 8 May 2025 09:41:15 +0700 Subject: (andri) add log note update field pada product variant --- indoteknik_custom/models/product_template.py | 83 +++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 8e475f95..99ae73d3 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -418,7 +418,10 @@ class ProductTemplate(models.Model): if val in [None, False]: return 'None' if isinstance(field, fields.Selection): - return dict(field.selection).get(val, str(val)) + selection = field.selection + if callable(selection): + selection = selection(record) + return dict(selection).get(val, str(val)) if isinstance(field, fields.Boolean): return 'Yes' if val else 'No' if isinstance(field, fields.Many2one): @@ -777,6 +780,84 @@ class ProductProduct(models.Model): ], limit=1) return pricelist + # simpan data lama + def _collect_old_values(self, vals): + return { + record.id: { + field_name: record[field_name] + for field_name in vals.keys() + if field_name in record._fields + } + for record in self + } + + # log perubahan field + def _log_field_changes(self, vals, old_values): + exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] + + for record in self: + changes = [] + for field_name in vals: + if field_name not in record._fields or field_name in exclude_fields: + continue + + field = record._fields[field_name] + field_label = field.string or field_name + old_value = old_values.get(record.id, {}).get(field_name) + new_value = record[field_name] + + def stringify(val): + if val in [None, False]: + return 'None' + + if isinstance(field, fields.Selection): + selection = field.selection + if callable(selection): + selection = selection(record) + return dict(selection).get(val, str(val)) + + if isinstance(field, fields.Boolean): + return 'Yes' if val else 'No' + + if isinstance(field, fields.Many2one): + if isinstance(val, int): + rec = self.env[field.comodel_name].browse(val) + return rec.display_name if rec.exists() else str(val) + elif isinstance(val, models.BaseModel): + return val.display_name + return str(val) + + if isinstance(field, fields.Many2many): + records = record[field_name] + names = [] + if records: + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + names = records.mapped(attr) + break + if not names: + names = [str(r.id) for r in records] + return ", ".join(names) if names else 'None' + + return str(val) + + + old_val_str = stringify(old_value) + new_val_str = stringify(new_value) + + if old_val_str != new_val_str: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + + if changes: + message = "Updated:
      %s
    " % "".join(changes) + record.message_post(body=message) + + # simpan data lama dan log perubahan field + def write(self, vals): + old_values = self._collect_old_values(vals) + result = super().write(vals) + self._log_field_changes(vals, old_values) + return result class OutstandingMove(models.Model): _name = 'v.move.outstanding' -- cgit v1.2.3 From bc89a3ae2ac20a53d46d8e50da4a3427f44bd870 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 8 May 2025 13:41:03 +0700 Subject: push --- indoteknik_custom/models/shipment_group.py | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/shipment_group.py b/indoteknik_custom/models/shipment_group.py index df3f1bb4..b7d7ac12 100644 --- a/indoteknik_custom/models/shipment_group.py +++ b/indoteknik_custom/models/shipment_group.py @@ -14,6 +14,13 @@ class ShipmentGroup(models.Model): number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True) shipment_line = fields.One2many('shipment.group.line', 'shipment_id', string='Shipment Group Lines', auto_join=True) partner_id = fields.Many2one('res.partner', string='Customer') + carrier_id = fields.Many2one('delivery.carrier', string='Ekspedisi') + total_colly_line = fields.Float(string='Total Colly', compute='_compute_total_colly_line') + + @api.depends('shipment_line.total_colly') + def _compute_total_colly_line(self): + for rec in self: + rec.total_colly_line = sum(rec.shipment_line.mapped('total_colly')) @api.model def create(self, vals): @@ -35,6 +42,26 @@ class ShipmentGroupLine(models.Model): ('indoteknik', 'Indoteknik'), ('customer', 'Customer') ], string='Shipping Paid by', copy=False) + total_colly = fields.Float(string='Total Colly') + carrier_id = fields.Many2one('delivery.carrier', string='Ekspedisi') + + @api.constrains('picking_id') + def _check_picking_id(self): + for rec in self: + if not rec.picking_id: + continue + + duplicates = self.env['shipment.group.line'].search([ + ('picking_id', '=', rec.picking_id.id), + ('id', '!=', rec.id) + ]) + + if duplicates: + shipment_numbers = duplicates.mapped('shipment_id.number') + raise UserError( + f"Picking {rec.picking_id.name} sudah discan dalam shipment group berikut: {', '.join(shipment_numbers)}! " + "Satu picking hanya boleh dimasukkan dalam satu shipment group." + ) @api.depends('picking_id.state') def _compute_state(self): @@ -63,12 +90,20 @@ class ShipmentGroupLine(models.Model): if self.shipment_id.partner_id and self.shipment_id.partner_id != picking.partner_id: raise UserError('Partner must be same as shipment group') + if self.shipment_id.carrier_id and self.shipment_id.carrier_id != picking.carrier_id: + raise UserError('carrier must be same as shipment group') + self.partner_id = picking.partner_id self.shipping_paid_by = picking.sale_id.shipping_paid_by + self.carrier_id = picking.carrier_id.id + self.total_colly = picking.total_mapping_koli if not self.shipment_id.partner_id: self.shipment_id.partner_id = picking.partner_id + if not self.shipment_id.carrier_id: + self.shipment_id.carrier_id = picking.carrier_id + self.sale_id = picking.sale_id @api.model -- cgit v1.2.3 From 35ed7159d12a5eb915f4c689535e2e8654b43e63 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 8 May 2025 17:09:56 +0700 Subject: (andri) add inherit mail.thread & mail.activity.mixim untuk log note pada pricelist --- indoteknik_custom/models/product_pricelist.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_pricelist.py b/indoteknik_custom/models/product_pricelist.py index ea3ee6cf..46d12f4d 100644 --- a/indoteknik_custom/models/product_pricelist.py +++ b/indoteknik_custom/models/product_pricelist.py @@ -3,8 +3,9 @@ from datetime import datetime, timedelta class ProductPricelist(models.Model): - _inherit = 'product.pricelist' - + _name = 'product.pricelist' + _inherit = ['product.pricelist', 'mail.thread', 'mail.activity.mixin'] + is_flash_sale = fields.Boolean(string='Flash Sale', default=False) is_show_program = fields.Boolean(string='Show Program', default=False) banner = fields.Binary(string='Banner') @@ -56,7 +57,8 @@ class ProductPricelist(models.Model): return tier_name class ProductPricelistItem(models.Model): - _inherit = 'product.pricelist.item' + _name = 'product.pricelist.item' + _inherit = ['product.pricelist.item', 'mail.thread', 'mail.activity.mixin'] manufacture_id = fields.Many2one('x_manufactures', string='Manufacture') computed_price = fields.Float(string='Computed Price') \ No newline at end of file -- cgit v1.2.3 From aca89f41cf980e2ea9b7f266d4792be52d2e4aa1 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Thu, 8 May 2025 22:12:07 +0700 Subject: (andri) add log note pada purchase list untuk catat perubahan tiap fields --- indoteknik_custom/models/purchase_pricelist.py | 67 +++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py index e5b35d7f..6ac74c69 100755 --- a/indoteknik_custom/models/purchase_pricelist.py +++ b/indoteknik_custom/models/purchase_pricelist.py @@ -6,6 +6,7 @@ from pytz import timezone class PurchasePricelist(models.Model): _name = 'purchase.pricelist' _rec_name = 'product_id' + _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char(string='Name', compute="_compute_name") product_id = fields.Many2one('product.product', string="Product", required=True) @@ -120,4 +121,68 @@ class PurchasePricelist(models.Model): rec.sync_pricelist_tier() rec.product_id.product_tmpl_id._create_solr_queue('_sync_price_to_solr') - \ No newline at end of file + + def _collect_old_values(self, vals): + return { + record.id: { + field_name: record[field_name] + for field_name in vals.keys() + if field_name in record._fields + } + for record in self + } + + def _log_field_changes(self, vals, old_values): + exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] + + for record in self: + changes = [] + for field_name in vals: + if field_name not in record._fields or field_name in exclude_fields: + continue + + field = record._fields[field_name] + field_label = field.string or field_name + old_value = old_values.get(record.id, {}).get(field_name) + new_value = record[field_name] + + def stringify(val): + if val in [None, False]: + return 'None' + if isinstance(field, fields.Selection): + selection = field.selection + if callable(selection): + selection = selection(record) + return dict(selection).get(val, str(val)) + if isinstance(field, fields.Boolean): + return 'Yes' if val else 'No' + if isinstance(field, fields.Many2one): + return val.name if hasattr(val, 'name') else str(val) + if isinstance(field, fields.Many2many): + records = val if isinstance(val, models.Model) else val + names = [] + if records: + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + names = records.mapped(attr) + break + if not names: + names = [str(r.id) for r in records] + return ", ".join(names) if names else 'None' + return str(val) + + old_val_str = stringify(old_value) + new_val_str = stringify(new_value) + + if old_val_str != new_val_str: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + + if changes: + message = "Updated:
      %s
    " % "".join(changes) + record.message_post(body=message) + + def write(self, vals): + old_values = self._collect_old_values(vals) + result = super(PurchasePricelist, self).write(vals) + self._log_field_changes(vals, old_values) + return result \ No newline at end of file -- cgit v1.2.3 From efba26bf68d142168d5189c6ee2b87c6d8a2299d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 9 May 2025 08:01:57 +0700 Subject: (andri) delete chatter pada product pricelist + item --- indoteknik_custom/models/product_pricelist.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_pricelist.py b/indoteknik_custom/models/product_pricelist.py index 46d12f4d..94a9b239 100644 --- a/indoteknik_custom/models/product_pricelist.py +++ b/indoteknik_custom/models/product_pricelist.py @@ -3,8 +3,7 @@ from datetime import datetime, timedelta class ProductPricelist(models.Model): - _name = 'product.pricelist' - _inherit = ['product.pricelist', 'mail.thread', 'mail.activity.mixin'] + _inherit = 'product.pricelist' is_flash_sale = fields.Boolean(string='Flash Sale', default=False) is_show_program = fields.Boolean(string='Show Program', default=False) @@ -57,8 +56,7 @@ class ProductPricelist(models.Model): return tier_name class ProductPricelistItem(models.Model): - _name = 'product.pricelist.item' - _inherit = ['product.pricelist.item', 'mail.thread', 'mail.activity.mixin'] + _inherit = 'product.pricelist.item' manufacture_id = fields.Many2one('x_manufactures', string='Manufacture') computed_price = fields.Float(string='Computed Price') \ No newline at end of file -- cgit v1.2.3 From 0f7e495ae7a28bd897929322e0419de91ecb1675 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 9 May 2025 11:21:46 +0700 Subject: (andri) penyesuaian log note pada purchase pricelist & handle perubahan log note image di product & variant (only notif add/update/delete) --- indoteknik_custom/models/product_template.py | 170 +++++++++++++++---------- indoteknik_custom/models/purchase_pricelist.py | 41 ++++-- 2 files changed, 127 insertions(+), 84 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 99ae73d3..373b8ebd 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -402,6 +402,11 @@ class ProductTemplate(models.Model): # log perubahan field def _log_field_changes(self, vals, old_values): exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] + custom_labels = { + 'image_1920': 'Main Image', + 'image_carousel_lines': 'Carousel Images', + 'product_template_image_ids': 'Extra Product Media', + } for record in self: changes = [] @@ -410,11 +415,11 @@ class ProductTemplate(models.Model): continue field = record._fields[field_name] - field_label = field.string or field_name + field_label = custom_labels.get(field_name, field.string or field_name) old_value = old_values.get(record.id, {}).get(field_name) new_value = record[field_name] # nilai setelah write - def stringify(val): + def stringify(val, field, record): if val in [None, False]: return 'None' if isinstance(field, fields.Selection): @@ -425,25 +430,40 @@ class ProductTemplate(models.Model): if isinstance(field, fields.Boolean): return 'Yes' if val else 'No' if isinstance(field, fields.Many2one): - return val.name if hasattr(val, 'name') else str(val) + if isinstance(val, int): + rec = record.env[field.comodel_name].browse(val) + return rec.display_name if rec.exists() else str(val) + elif isinstance(val, models.BaseModel): + return val.display_name + return str(val) if isinstance(field, fields.Many2many): - records = val if isinstance(val, models.Model) else val - names = [] - if records: - for attr in ['name', 'x_name', 'display_name']: - if hasattr(records[0], attr): - names = records.mapped(attr) - break - if not names: - names = [str(r.id) for r in records] - return ", ".join(names) if names else 'None' + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + return ", ".join(records.mapped(attr)) + return ", ".join(str(r.id) for r in records) + if isinstance(field, fields.One2many): + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' + return f"{field.comodel_name}({', '.join(str(r.id) for r in records)})" return str(val) - old_val_str = stringify(old_value) - new_val_str = stringify(new_value) + old_val_str = stringify(old_value, field, record) + new_val_str = stringify(new_value, field, record) if old_val_str != new_val_str: - changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + if field_name in custom_labels: + if old_val_str == 'None' and new_val_str != 'None': + changes.append(f"
  • {field_label}: image added
  • ") + elif old_val_str != 'None' and new_val_str == 'None': + changes.append(f"
  • {field_label}: image removed
  • ") + else: + changes.append(f"
  • {field_label}: image updated
  • ") + else: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") if changes: message = "Updated:
      %s
    " % "".join(changes) @@ -793,64 +813,74 @@ class ProductProduct(models.Model): # log perubahan field def _log_field_changes(self, vals, old_values): - exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] - - for record in self: - changes = [] - for field_name in vals: - if field_name not in record._fields or field_name in exclude_fields: - continue - - field = record._fields[field_name] - field_label = field.string or field_name - old_value = old_values.get(record.id, {}).get(field_name) - new_value = record[field_name] + exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] + # for image fields, use custom labels + custom_labels = { + 'image_1920': 'Main Image', + 'image_carousel_lines': 'Carousel Images', + 'product_template_image_ids': 'Extra Product Media', + } - def stringify(val): - if val in [None, False]: - return 'None' - - if isinstance(field, fields.Selection): - selection = field.selection - if callable(selection): - selection = selection(record) - return dict(selection).get(val, str(val)) - - if isinstance(field, fields.Boolean): - return 'Yes' if val else 'No' - - if isinstance(field, fields.Many2one): - if isinstance(val, int): - rec = self.env[field.comodel_name].browse(val) - return rec.display_name if rec.exists() else str(val) - elif isinstance(val, models.BaseModel): - return val.display_name - return str(val) - - if isinstance(field, fields.Many2many): - records = record[field_name] - names = [] - if records: + for record in self: + changes = [] + for field_name in vals: + if field_name not in record._fields or field_name in exclude_fields: + continue + + field = record._fields[field_name] + field_label = custom_labels.get(field_name, field.string or field_name) + old_value = old_values.get(record.id, {}).get(field_name) + new_value = record[field_name] # nilai setelah write + + def stringify(val, field, record): + if val in [None, False]: + return 'None' + if isinstance(field, fields.Selection): + selection = field.selection + if callable(selection): + selection = selection(record) + return dict(selection).get(val, str(val)) + if isinstance(field, fields.Boolean): + return 'Yes' if val else 'No' + if isinstance(field, fields.Many2one): + if isinstance(val, int): + rec = record.env[field.comodel_name].browse(val) + return rec.display_name if rec.exists() else str(val) + elif isinstance(val, models.BaseModel): + return val.display_name + return str(val) + if isinstance(field, fields.Many2many): + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' for attr in ['name', 'x_name', 'display_name']: if hasattr(records[0], attr): - names = records.mapped(attr) - break - if not names: - names = [str(r.id) for r in records] - return ", ".join(names) if names else 'None' - - return str(val) - - - old_val_str = stringify(old_value) - new_val_str = stringify(new_value) - - if old_val_str != new_val_str: - changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + return ", ".join(records.mapped(attr)) + return ", ".join(str(r.id) for r in records) + if isinstance(field, fields.One2many): + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' + return f"{field.comodel_name}({', '.join(str(r.id) for r in records)})" + return str(val) - if changes: - message = "Updated:
      %s
    " % "".join(changes) - record.message_post(body=message) + old_val_str = stringify(old_value, field, record) + new_val_str = stringify(new_value, field, record) + + if old_val_str != new_val_str: + if field_name in custom_labels: # handle image field + if old_val_str == 'None' and new_val_str != 'None': + changes.append(f"
  • {field_label}: image added
  • ") + elif old_val_str != 'None' and new_val_str == 'None': + changes.append(f"
  • {field_label}: image removed
  • ") + else: + changes.append(f"
  • {field_label}: image updated
  • ") + else: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + + if changes: + message = "Updated:
      %s
    " % "".join(changes) + record.message_post(body=message) # simpan data lama dan log perubahan field def write(self, vals): diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py index 6ac74c69..764911cf 100755 --- a/indoteknik_custom/models/purchase_pricelist.py +++ b/indoteknik_custom/models/purchase_pricelist.py @@ -146,33 +146,46 @@ class PurchasePricelist(models.Model): old_value = old_values.get(record.id, {}).get(field_name) new_value = record[field_name] - def stringify(val): + def stringify(val, field, record): if val in [None, False]: return 'None' + # Handle Selection if isinstance(field, fields.Selection): selection = field.selection if callable(selection): selection = selection(record) return dict(selection).get(val, str(val)) + # Handle Boolean if isinstance(field, fields.Boolean): return 'Yes' if val else 'No' + # Handle Many2one if isinstance(field, fields.Many2one): - return val.name if hasattr(val, 'name') else str(val) + if isinstance(val, int): + rec = record.env[field.comodel_name].browse(val) + return rec.display_name if rec.exists() else str(val) + elif isinstance(val, models.BaseModel): + return val.display_name + return str(val) + # Handle Many2many if isinstance(field, fields.Many2many): - records = val if isinstance(val, models.Model) else val - names = [] - if records: - for attr in ['name', 'x_name', 'display_name']: - if hasattr(records[0], attr): - names = records.mapped(attr) - break - if not names: - names = [str(r.id) for r in records] - return ", ".join(names) if names else 'None' + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + return ", ".join(records.mapped(attr)) + return ", ".join(str(r.id) for r in records) + # Handle One2many + if isinstance(field, fields.One2many): + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' + return f"{field.comodel_name}({', '.join(str(r.id) for r in records)})" + # Default case (Char, Float, Integer, etc) return str(val) - old_val_str = stringify(old_value) - new_val_str = stringify(new_value) + old_val_str = stringify(old_value, field, record) + new_val_str = stringify(new_value, field, record) if old_val_str != new_val_str: changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") -- cgit v1.2.3 From 10d6ffad06f2dd89eca972257c2b9326d002949a Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 9 May 2025 13:35:42 +0700 Subject: add partner group by in tree view --- indoteknik_custom/models/commision.py | 47 ++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 788fc0f9..eeaa8efc 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -201,7 +201,8 @@ class CustomerCommision(models.Model): grouped_invoice_number = fields.Char(string='Group Invoice Number', compute='_compute_grouped_numbers') sales_id = fields.Many2one('res.users', string="Sales", tracking=True, default=lambda self: self.env.user, - domain=lambda self: [('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman').id)]) + domain=lambda self: [ + ('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman').id)]) date_approved_sales = fields.Datetime(string="Date Approved Sales", tracking=True) date_approved_marketing = fields.Datetime(string="Date Approved Marketing", tracking=True) @@ -213,6 +214,46 @@ class CustomerCommision(models.Model): position_pimpinan = fields.Char(string="Position Pimpinan", tracking=True) position_accounting = fields.Char(string="Position Accounting", tracking=True) + # get partner ids so it can be grouped by + @api.model + def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): + if 'partner_ids' in groupby: + # Get all records matching the domain + records = self.search(domain) + + # Create groups for each partner + groups = {} + for record in records: + for partner in record.partner_ids: + if partner.id not in groups: + groups[partner.id] = { + 'partner_ids': partner, + 'records': self.env['customer.commision'] + } + groups[partner.id]['records'] |= record + + # Format the result + result = [] + for partner_id, group_data in groups.items(): + partner = group_data['partner_ids'] + record_ids = group_data['records'].ids + + # Create the domain + group_domain = [('id', 'in', record_ids)] + if domain: + group_domain = ['&'] + domain + group_domain + + result.append({ + 'partner_ids': (partner.id, partner.display_name), + 'partner_ids_count': len(record_ids), + '__domain': group_domain, + '__count': len(record_ids), + }) + + return result + + return super(CustomerCommision, self).read_group(domain, fields, groupby, offset, limit, orderby, lazy) + def compute_delivery_amt_text(self): tb = Terbilang() @@ -308,9 +349,9 @@ class CustomerCommision(models.Model): def action_confirm_customer_commision(self): jakarta_tz = pytz.timezone('Asia/Jakarta') now = datetime.now(jakarta_tz) - + now_naive = now.replace(tzinfo=None) - + if not self.status or self.status == 'draft': self.status = 'pengajuan1' elif self.status == 'pengajuan1' and self.env.user.is_sales_manager: -- cgit v1.2.3 From 474755658e732235a7855810b993184c23f75386 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 9 May 2025 15:20:00 +0700 Subject: (andri) penyesuaian log note untuk perubahan nilai pada tabel vendor pricelist & product attribute --- indoteknik_custom/models/product_template.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 373b8ebd..e95060e6 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -406,6 +406,8 @@ class ProductTemplate(models.Model): 'image_1920': 'Main Image', 'image_carousel_lines': 'Carousel Images', 'product_template_image_ids': 'Extra Product Media', + 'seller_ids': 'Vendor Pricelist', + 'attribute_line_ids': 'Product Attributes', } for record in self: @@ -457,11 +459,11 @@ class ProductTemplate(models.Model): if old_val_str != new_val_str: if field_name in custom_labels: if old_val_str == 'None' and new_val_str != 'None': - changes.append(f"
  • {field_label}: image added
  • ") + changes.append(f"
  • {field_label}: added
  • ") elif old_val_str != 'None' and new_val_str == 'None': - changes.append(f"
  • {field_label}: image removed
  • ") + changes.append(f"
  • {field_label}: removed
  • ") else: - changes.append(f"
  • {field_label}: image updated
  • ") + changes.append(f"
  • {field_label}: updated
  • ") else: changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") -- cgit v1.2.3 From 81a60ec161deb7eba3072172744276d6e5457f64 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 9 May 2025 15:27:38 +0700 Subject: integration api kgx --- indoteknik_custom/models/stock_picking.py | 74 +++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ccccbcdc..dd2365ec 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -255,6 +255,13 @@ class StockPicking(models.Model): lalamove_image_url = fields.Char(string="Lalamove Image URL") lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html") + # KGX Section + kgx_pod_photo_url = fields.Char('KGX Photo URL') + kgx_pod_photo = fields.Html('KGX Photo', compute='_compute_kgx_image_html') + kgx_pod_signature = fields.Char('KGX Signature URL') + kgx_pod_receive_time = fields.Datetime('KGX Ata Date') + kgx_pod_receiver = fields.Char('KGX Receiver') + total_koli = fields.Integer(compute='_compute_total_koli', string="Total Koli") total_koli_display = fields.Char(compute='_compute_total_koli_display', string="Total Koli Display") linked_out_picking_id = fields.Many2one('stock.picking', string="Linked BU/OUT", copy=False) @@ -276,6 +283,66 @@ class StockPicking(models.Model): last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') + def _get_kgx_awb_number(self): + """Menggabungkan name dan origin untuk membuat AWB Number""" + self.ensure_one() + if not self.name or not self.origin: + return False + return f"{self.name} {self.origin}" + + def _download_pod_photo(self, url): + """Mengunduh foto POD dari URL""" + try: + response = requests.get(url, timeout=10) + response.raise_for_status() + return base64.b64encode(response.content) + except Exception as e: + raise UserError(f"Gagal mengunduh foto POD: {str(e)}") + + def _parse_datetime(self, dt_str): + """Parse datetime string dari format KGX""" + try: + from datetime import datetime + # Hilangkan timezone jika ada masalah parsing + if '+' in dt_str: + dt_str = dt_str.split('+')[0] + return datetime.strptime(dt_str, '%Y-%m-%dT%H:%M:%S') + except ValueError: + return False + + def action_get_kgx_pod(self): + self.ensure_one() + + awb_number = self._get_kgx_awb_number() + if not awb_number: + raise UserError("Nomor AWB tidak dapat dibuat, pastikan picking memiliki name dan origin") + + url = "https://kgx.co.id/get_detail_awb" + headers = {'Content-Type': 'application/json'} + payload = {"params" : {'awb_number': awb_number}} + + try: + response = requests.post(url, headers=headers, data=json.dumps(payload)) + response.raise_for_status() + data = response.json() + + if data.get('result', {}).get('data', []): + pod_data = data['result']['data'][0].get('connote_pod', {}) + photo_url = pod_data.get('photo') + + self.kgx_pod_photo_url = photo_url + self.kgx_pod_signature = pod_data.get('signature') + self.kgx_pod_receiver = pod_data.get('receiver') + self.kgx_pod_receive_time = self._parse_datetime(pod_data.get('timeReceive')) + self.driver_arrival_date = self._parse_datetime(pod_data.get('timeReceive')) + + return data + else: + raise UserError(f"Tidak ditemukan data untuk AWB: {awb_number}") + + except requests.exceptions.RequestException as e: + raise UserError(f"Gagal mengambil data POD: {str(e)}") + @api.constrains('sj_return_date') def _check_sj_return_date(self): for record in self: @@ -454,6 +521,13 @@ class StockPicking(models.Model): else: record.lalamove_image_html = "No image available." + def _compute_kgx_image_html(self): + for record in self: + if record.kgx_pod_photo_url: + record.kgx_pod_photo = f'' + else: + record.kgx_pod_photo = "No image available." + def action_fetch_lalamove_order(self): pickings = self.env['stock.picking'].search([ ('picking_type_code', '=', 'outgoing'), -- cgit v1.2.3 From 0362205a2ac1404fffccdd02691f121a4ffac76e Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 10 May 2025 11:59:11 +0700 Subject: (andri) perapihan log note product di bagian vendor dan product attribute serta penyesuaian yang lain --- indoteknik_custom/models/product_template.py | 476 ++++++++++++++++++++++++--- 1 file changed, 439 insertions(+), 37 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index e95060e6..19de8bb7 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -388,8 +388,39 @@ class ProductTemplate(models.Model): self.env['token.storage'].create([values]) return values - # simpan data lama + # ============================== + def get_vendor_name(self, rec): + """Get formatted name for vendor/supplier""" + return rec.name.name if rec.name else f"ID {rec.id}" + + def get_attribute_line_name(self, rec): + """Get formatted name for attribute line""" + if rec.attribute_id and rec.value_ids: + values = ", ".join(rec.value_ids.mapped('name')) + return f"{rec.attribute_id.name}: {values}" + return f"ID {rec.id}" + + def _get_vendor_field_label(self, field_name): + """Get human-readable label for vendor fields""" + field_labels = { + 'name': 'Vendor', + 'currency_id': 'Currency', + 'product_uom': 'Unit of Measure', + 'price': 'Price', + 'delay': 'Delivery Lead Time', + 'product_id': 'Product Variant', + 'product_name': 'Vendor Product Name', + 'product_code': 'Vendor Product Code', + 'date_start': 'Start Date', + 'date_end': 'End Date', + 'min_qty': 'Quantity' + } + return field_labels.get(field_name, field_name.replace('_', ' ').title()) + + # ============================== + def _collect_old_values(self, vals): + """Collect old values before write""" return { record.id: { field_name: record[field_name] @@ -397,37 +428,417 @@ class ProductTemplate(models.Model): if field_name in record._fields } for record in self - } - - # log perubahan field - def _log_field_changes(self, vals, old_values): - exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] - custom_labels = { + } + + def _prepare_attribute_line_info(self): + """Prepare attribute line info for logging and update comparison""" + line_info = {} + for line in self.attribute_line_ids: + line_info[line.id] = { + 'name': self.get_attribute_line_name(line), + 'attribute_id': line.attribute_id.id if line.attribute_id else None, + 'attribute_name': line.attribute_id.name if line.attribute_id else None, + 'value_ids': [(v.id, v.name) for v in line.value_ids], + 'value_names': ", ".join(line.value_ids.mapped('name')) + } + return line_info + + def _prepare_vendor_info(self): + """Prepare vendor info for logging before they are deleted""" + vendor_info = {} + for seller in self.seller_ids: + vendor_info[seller.id] = { + 'name': self.get_vendor_name(seller), + 'price': seller.price, + 'min_qty': seller.min_qty, + 'delay': seller.delay, + 'product_name': seller.product_name, + 'product_code': seller.product_code, + 'currency_id': seller.currency_id.id if seller.currency_id else None, + 'product_uom': seller.product_uom.id if seller.product_uom else None, + 'product_id': seller.product_id.id if seller.product_id else None, + 'date_start': seller.date_start, + 'date_end': seller.date_end, + } + return vendor_info + + # ========================== + + def _get_context_with_all_info(self, vals): + """Get context with all necessary info (attributes and vendors)""" + context = dict(self.env.context) + + # Check for attribute line changes + if 'attribute_line_ids' in vals: + attribute_line_info = {} + for product in self: + product_line_info = product._prepare_attribute_line_info() + attribute_line_info.update(product_line_info) + context['attribute_line_info'] = attribute_line_info + + # Check for vendor changes - store both for deletion and for comparing old values + if 'seller_ids' in vals: + vendor_info = {} + vendor_old_values = {} + for product in self: + # For deletion logging + product_vendor_info = product._prepare_vendor_info() + vendor_info.update(product_vendor_info) + + # For update comparison + product_vendor_old = product._prepare_vendor_info() + vendor_old_values.update(product_vendor_old) + + context['vendor_info'] = vendor_info + context['vendor_old_values'] = vendor_old_values + + return context + + # ======================== + + def _log_image_changes(self, field_name, old_val, new_val): + """Log image field changes""" + label_map = { 'image_1920': 'Main Image', 'image_carousel_lines': 'Carousel Images', 'product_template_image_ids': 'Extra Product Media', - 'seller_ids': 'Vendor Pricelist', - 'attribute_line_ids': 'Product Attributes', } + label = label_map.get(field_name, field_name) + + if old_val == 'None' and new_val != 'None': + return f"
  • {label}: image added
  • " + elif old_val != 'None' and new_val == 'None': + return f"
  • {label}: image removed
  • " + elif old_val != new_val: + return f"
  • {label}: image updated
  • " + return None + + def _log_attribute_line_changes(self, commands): + """Log changes to attribute lines with complete information""" + # Get stored info from context + stored_info = self.env.context.get('attribute_line_info', {}) + + for cmd in commands: + if cmd[0] == 0: # Add + new = self.env['product.template.attribute.line'].new(cmd[2]) + attribute_name = new.attribute_id.name if new.attribute_id else 'Attribute' + values = ", ".join(new.value_ids.mapped('name')) if new.value_ids else '' + + message = f"Product Attribute:
    {attribute_name} added
    " + if values: + message += f"Values: '{values}'" + self.message_post(body=message) + + elif cmd[0] == 1: # Update + rec_id = cmd[1] + vals = cmd[2] + + # Get old values from context + old_data = stored_info.get(rec_id, {}) + if not old_data: + # Fallback: get current record + rec = self.env['product.template.attribute.line'].browse(rec_id) + if not rec.exists(): + continue + old_data = { + 'name': self.get_attribute_line_name(rec), + 'attribute_id': rec.attribute_id.id if rec.attribute_id else None, + 'attribute_name': rec.attribute_id.name if rec.attribute_id else None, + 'value_ids': [(v.id, v.name) for v in rec.value_ids], + 'value_names': ", ".join(rec.value_ids.mapped('name')) + } + + changes = [] + attribute_name = old_data.get('attribute_name', 'Attribute') + + # Check for attribute change + if 'attribute_id' in vals: + old_attr = old_data.get('attribute_name', '-') + new_attr = self.env['product.attribute'].browse(vals['attribute_id']).name + if old_attr != new_attr: + attribute_name = new_attr # Update attribute name for display + changes.append(f"Attribute changed from '{old_attr}' to '{new_attr}'") + + # Check for value changes + if 'value_ids' in vals: + old_vals = old_data.get('value_names', '') + + # Parse the command for value_ids + new_value_ids = [] + for value_cmd in vals['value_ids']: + if isinstance(value_cmd, (list, tuple)): + if value_cmd[0] == 6: # Replace all + new_value_ids = value_cmd[2] + elif value_cmd[0] == 4: # Add + new_value_ids.append(value_cmd[1]) + elif value_cmd[0] == 3: # Remove + # This is more complex, would need current state + pass + + # Get new value names + if new_value_ids: + new_values = self.env['product.attribute.value'].browse(new_value_ids) + new_vals = ", ".join(new_values.mapped('name')) + else: + new_vals = "" + + if old_vals != new_vals: + changes.append(f"Values: '{old_vals}' → '{new_vals}'") + + if changes: + # Format with attribute name + message = f"Product Attribute:
    {attribute_name} updated
    " + message += "
    ".join(changes) + self.message_post(body=message) + + elif cmd[0] in (2, 3): # Remove + # Use info from stored data + line_data = stored_info.get(cmd[1]) + if line_data: + attribute_name = line_data.get('attribute_name', 'Attribute') + values = line_data.get('value_names', '') + else: + rec = self.env['product.template.attribute.line'].browse(cmd[1]) + if rec.exists(): + attribute_name = rec.attribute_id.name if rec.attribute_id else 'Attribute' + values = ", ".join(rec.value_ids.mapped('name')) if rec.value_ids else '' + else: + attribute_name = 'Attribute' + values = f"ID {cmd[1]}" + + message = f"Product Attribute:
    {attribute_name} removed
    " + if values: + message += f"Values: '{values}'" + self.message_post(body=message) + + elif cmd[0] == 5: # Clear all + self.message_post(body=f"Product Attribute:
    All attributes removed") + + def _log_vendor_pricelist_changes(self, commands): + """Log changes to vendor pricelist with complete information""" + # Get stored info from context + stored_info = self.env.context.get('vendor_info', {}) + old_values_info = self.env.context.get('vendor_old_values', {}) + + for cmd in commands: + if cmd[0] == 0: # Add + vals = cmd[2] + + # Create temporary record to get proper display values + temp_values = vals.copy() + temp_values['product_tmpl_id'] = self.id + new = self.env['product.supplierinfo'].new(temp_values) + + name = self.get_vendor_name(new) + details = [] + + if 'price' in vals and vals['price'] is not None: + details.append(f"
  • Price: {vals['price']}
  • ") + if 'min_qty' in vals and vals['min_qty'] is not None: + details.append(f"
  • Quantity: {vals['min_qty']}
  • ") + if 'delay' in vals and vals['delay'] is not None: + details.append(f"
  • Delivery Lead Time: {vals['delay']}
  • ") + if 'product_name' in vals and vals['product_name']: + details.append(f"
  • Vendor Product Name: {vals['product_name']}
  • ") + if 'product_code' in vals and vals['product_code']: + details.append(f"
  • Vendor Product Code: {vals['product_code']}
  • ") + if 'currency_id' in vals and vals['currency_id']: + currency = self.env['res.currency'].browse(vals['currency_id']) + details.append(f"
  • Currency: {currency.name}
  • ") + if 'product_uom' in vals and vals['product_uom']: + uom = self.env['uom.uom'].browse(vals['product_uom']) + details.append(f"
  • Unit of Measure: {uom.name}
  • ") + + if details: + detail_str = f" with:
      {''.join(details)}
    " + else: + detail_str = "" + + self.message_post(body=f"Vendor Pricelist: added '{name}'{detail_str}") + + elif cmd[0] == 1: # Update + rec_id = cmd[1] + vals = cmd[2] + + # Get old values from context + old_data = old_values_info.get(rec_id, {}) + if not old_data: + # Fallback: get current record + rec = self.env['product.supplierinfo'].browse(rec_id) + if not rec.exists(): + continue + old_data = { + 'name': self.get_vendor_name(rec), + 'price': rec.price, + 'min_qty': rec.min_qty, + 'delay': rec.delay, + 'product_name': rec.product_name, + 'product_code': rec.product_code, + 'currency_id': rec.currency_id.id if rec.currency_id else None, + 'product_uom': rec.product_uom.id if rec.product_uom else None, + 'product_id': rec.product_id.id if rec.product_id else None, + 'date_start': rec.date_start, + 'date_end': rec.date_end, + } + + name = old_data.get('name', f'ID {rec_id}') + changes = [] + + # Check each field in vals for changes + for field, new_value in vals.items(): + if field == 'name': + # Special handling for vendor name change + if new_value != old_data.get('name'): + old_name = old_data.get('name', 'None') + new_name = self.env['res.partner'].browse(new_value).name if new_value else 'None' + changes.append(f"
  • Vendor: {old_name} → {new_name}
  • ") + continue + + old_value = old_data.get(field) + + # Format values based on field type + if field == 'currency_id': + if old_value != new_value: + old_str = self.env['res.currency'].browse(old_value).name if old_value else 'None' + new_str = self.env['res.currency'].browse(new_value).name if new_value else 'None' + else: + continue + elif field == 'product_uom': + if old_value != new_value: + old_str = self.env['uom.uom'].browse(old_value).name if old_value else 'None' + new_str = self.env['uom.uom'].browse(new_value).name if new_value else 'None' + else: + continue + elif field == 'product_id': + if old_value != new_value: + old_str = self.env['product.product'].browse(old_value).display_name if old_value else 'None' + new_str = self.env['product.product'].browse(new_value).display_name if new_value else 'None' + else: + continue + elif field in ['date_start', 'date_end']: + if str(old_value) != str(new_value): + old_str = old_value.strftime('%Y-%m-%d') if old_value else 'None' + new_str = new_value if new_value else 'None' + else: + continue + else: + # For numeric and other fields + if field in ['price', 'min_qty', 'delay']: + # Compare numeric values properly + old_num = float(old_value) if old_value is not None else 0.0 + new_num = float(new_value) if new_value is not None else 0.0 + + if field == 'delay': # Integer field + old_num = int(old_num) + new_num = int(new_num) + + if old_num == new_num: + continue + + old_str = str(old_value) if old_value is not None else 'None' + new_str = str(new_value) if new_value is not None else 'None' + else: + # String and other types + if str(old_value) == str(new_value): + continue + old_str = str(old_value) if old_value is not None else 'None' + new_str = str(new_value) if new_value is not None else 'None' + + label = self._get_vendor_field_label(field) + changes.append(f"
  • {label}: {old_str} → {new_str}
  • ") + + if changes: + changes_str = f"
      {''.join(changes)}
    " + self.message_post(body=f"Vendor Pricelist: updated '{name}':{changes_str}") + + elif cmd[0] in (2, 3): # Remove + vendor_data = stored_info.get(cmd[1]) + if vendor_data: + name = vendor_data['name'] + details = [] + + if vendor_data.get('price'): + details.append(f"
  • Price: {vendor_data['price']}
  • ") + if vendor_data.get('min_qty'): + details.append(f"
  • Quantity: {vendor_data['min_qty']}
  • ") + if vendor_data.get('product_name'): + details.append(f"
  • Product Name: {vendor_data['product_name']}
  • ") + if vendor_data.get('delay'): + details.append(f"
  • Delivery Lead Time: {vendor_data['delay']}
  • ") + + if details: + detail_str = f"
      {''.join(details)}
    " + else: + detail_str = "" + else: + rec = self.env['product.supplierinfo'].browse(cmd[1]) + if rec.exists(): + name = self.get_vendor_name(rec) + details = [] + if rec.price: + details.append(f"
  • Price: {rec.price}
  • ") + if rec.min_qty: + details.append(f"
  • Quantity: {rec.min_qty}
  • ") + if rec.product_name: + details.append(f"
  • Product Name: {rec.product_name}
  • ") + + if details: + detail_str = f"
      {''.join(details)}
    " + else: + detail_str = "" + else: + name = f"ID {cmd[1]}" + detail_str = "" + + self.message_post(body=f"Vendor Pricelist: removed '{name}'{detail_str}") + + elif cmd[0] == 5: # Clear all + self.message_post(body=f"Vendor Pricelist: all removed") + + def _log_field_changes_product(self, vals, old_values): + """Log general field changes for product template""" + exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] + image_fields = ['image_1920', 'image_carousel_lines', 'product_template_image_ids'] + for record in self: changes = [] + for field_name in vals: if field_name not in record._fields or field_name in exclude_fields: continue field = record._fields[field_name] - field_label = custom_labels.get(field_name, field.string or field_name) - old_value = old_values.get(record.id, {}).get(field_name) - new_value = record[field_name] # nilai setelah write - def stringify(val, field, record): + # Handle image fields specially + if field_name in image_fields: + old_val = 'None' if not old_values.get(record.id, {}).get(field_name) else 'Yes' + new_val = 'None' if not record[field_name] else 'Yes' + image_msg = record._log_image_changes(field_name, old_val, new_val) + if image_msg: + changes.append(image_msg) + continue + + # Handle vendor fields + if field_name == 'seller_ids': + commands = vals[field_name] + if isinstance(commands, list): + record._log_vendor_pricelist_changes(commands) + continue + + # Handle attribute lines + if field_name == 'attribute_line_ids': + commands = vals[field_name] + if isinstance(commands, list): + record._log_attribute_line_changes(commands) + continue + + # Handle other fields + def stringify(val): if val in [None, False]: return 'None' if isinstance(field, fields.Selection): - selection = field.selection - if callable(selection): - selection = selection(record) + selection = field.selection(record) if callable(field.selection) else field.selection return dict(selection).get(val, str(val)) if isinstance(field, fields.Boolean): return 'Yes' if val else 'No' @@ -446,36 +857,27 @@ class ProductTemplate(models.Model): if hasattr(records[0], attr): return ", ".join(records.mapped(attr)) return ", ".join(str(r.id) for r in records) - if isinstance(field, fields.One2many): - records = val if isinstance(val, models.BaseModel) else record[field.name] - if not records: - return 'None' - return f"{field.comodel_name}({', '.join(str(r.id) for r in records)})" return str(val) - old_val_str = stringify(old_value, field, record) - new_val_str = stringify(new_value, field, record) + old_val_str = stringify(old_values.get(record.id, {}).get(field_name)) + new_val_str = stringify(record[field_name]) if old_val_str != new_val_str: - if field_name in custom_labels: - if old_val_str == 'None' and new_val_str != 'None': - changes.append(f"
  • {field_label}: added
  • ") - elif old_val_str != 'None' and new_val_str == 'None': - changes.append(f"
  • {field_label}: removed
  • ") - else: - changes.append(f"
  • {field_label}: updated
  • ") - else: - changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + field_label = field.string or field_name + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") if changes: - message = "Updated:
      %s
    " % "".join(changes) - record.message_post(body=message) + record.message_post(body=f"Updated:
      {''.join(changes)}
    ") # simpan data lama dan log perubahan field def write(self, vals): + context = self._get_context_with_all_info(vals) + if context != self.env.context: + self = self.with_context(**context) old_values = self._collect_old_values(vals) result = super().write(vals) - self._log_field_changes(vals, old_values) + # Log changes + self._log_field_changes_product(vals, old_values) return result # def write(self, vals): @@ -814,7 +1216,7 @@ class ProductProduct(models.Model): } # log perubahan field - def _log_field_changes(self, vals, old_values): + def _log_field_changes_product_variants(self, vals, old_values): exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] # for image fields, use custom labels custom_labels = { @@ -888,7 +1290,7 @@ class ProductProduct(models.Model): def write(self, vals): old_values = self._collect_old_values(vals) result = super().write(vals) - self._log_field_changes(vals, old_values) + self._log_field_changes_product_variants(vals, old_values) return result class OutstandingMove(models.Model): -- cgit v1.2.3 From ad3eb842d881ad89b0239075695dc2ccf424031f Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 10 May 2025 15:36:55 +0700 Subject: (andri) add log note PO Confirm di purchase Pricelist --- indoteknik_custom/models/purchase_order.py | 122 ++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 98b367d0..a3e2c388 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -580,10 +580,11 @@ class PurchaseOrder(models.Model): purchase_pricelist = self.env['purchase.pricelist'].search([ ('product_id', '=', line.product_id.id), ('vendor_id', '=', line.order_id.partner_id.id) - ]) - purchase_pricelist = purchase_pricelist.with_context(update_by='system') + ]) + if not purchase_pricelist: - purchase_pricelist.create([{ + # Buat pricelist baru dengan context + new_pricelist = self.env['purchase.pricelist'].with_context(update_by='system').create([{ 'vendor_id': line.order_id.partner_id.id, 'product_id': line.product_id.id, 'product_price': 0, @@ -591,12 +592,52 @@ class PurchaseOrder(models.Model): 'system_price': price_unit, 'system_last_update': current_time }]) + + # Buat lognote untuk pricelist baru + message = f""" + New Purchase Pricelist Created from PO
    + PO: {line.order_id.name}
    + System Price: {price_unit:,.2f}
    + System Tax: {taxes.name if taxes else 'No Tax'}
    + System Update: {current_time}
    + """ + new_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id) else: + # Simpan nilai lama untuk logging + old_values = { + 'system_price': purchase_pricelist.system_price, + 'taxes_system_id': purchase_pricelist.taxes_system_id, + } + + # Update dengan context + purchase_pricelist = purchase_pricelist.with_context(update_by='system') purchase_pricelist.write({ 'system_last_update': current_time, 'taxes_system_id': taxes.id, 'system_price': price_unit }) + + # Buat lognote jika ada perubahan + changes = [] + if old_values['system_price'] != price_unit: + changes.append(f"
  • System Price: {old_values['system_price']:,.2f} → {price_unit:,.2f}
  • ") + if old_values['taxes_system_id'] != taxes: + old_tax_name = old_values['taxes_system_id'].name if old_values['taxes_system_id'] else 'No Tax' + new_tax_name = taxes.name if taxes else 'No Tax' + changes.append(f"
  • System Tax: {old_tax_name} → {new_tax_name}
  • ") + + if changes: + message = f""" + System Fields Updated from PO
    + PO: {line.order_id.name}
    + Changes: +
      + {"".join(changes)} +
    • System Update: {current_time}
    • +
    + Updated By: {self.env.user.name} + """ + purchase_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id) def _compute_date_planned(self): for order in self: @@ -1198,8 +1239,83 @@ class PurchaseOrder(models.Model): if not self.env.context.get('skip_check_payment'): self.with_context(skip_check_payment=True)._check_payment_term() self.with_context(notify_tax=True)._check_tax_rule() + # Tambahkan pemanggilan method untuk handle pricelist system update + self._handle_pricelist_system_update(vals) return res + def _handle_pricelist_system_update(self, vals): + if 'order_line' in vals or any(key in vals for key in ['state', 'approval_status']): + for order in self: + # Hanya proses jika PO sudah approved + if order.state in ['purchase', 'done'] and order.approval_status == 'approved': + self._process_pricelist_update(order) + + def _process_pricelist_update(self, order): + for line in order.order_line: + pricelist = self._get_related_pricelist(line.product_id, order.partner_id) + + if pricelist: + # Simpan nilai lama + old_values = self._get_pricelist_old_values(pricelist) + + # Update dan cek perubahan + self._update_and_log_pricelist(pricelist, line, old_values) + + def _get_related_pricelist(self, product_id, vendor_id): + return self.env['purchase.pricelist'].search([ + ('product_id', '=', product_id.id), + ('vendor_id', '=', vendor_id.id) + ], limit=1) + + def _get_pricelist_old_values(self, pricelist): + return { + 'system_price': pricelist.system_price, + 'taxes_system_id': pricelist.taxes_system_id, + 'system_last_update': pricelist.system_last_update + } + + def _update_and_log_pricelist(self, pricelist, po_line, old_values): + changes = [] + current_time = fields.Datetime.now() + + # Cek perubahan System Price + if pricelist.system_price != po_line.price_unit: + if old_values['system_price'] != po_line.price_unit: + changes.append(f"
  • System Price: {old_values['system_price']:,.2f} → {po_line.price_unit:,.2f}
  • ") + + # Cek perubahan System Tax + if pricelist.taxes_system_id != po_line.taxes_id: + old_tax = old_values['taxes_system_id'] + old_tax_name = old_tax.name if old_tax else 'No Tax' + new_tax_name = po_line.taxes_id.name if po_line.taxes_id else 'No Tax' + if old_tax != po_line.taxes_id: + changes.append(f"
  • System Tax: {old_tax_name} → {new_tax_name}
  • ") + + # Update fields jika ada perubahan + if changes: + pricelist.with_context(update_by='system').write({ + 'system_price': po_line.price_unit, + 'taxes_system_id': po_line.taxes_id.id if po_line.taxes_id else False, + 'system_last_update': current_time + }) + + # Buat lognote + self._create_pricelist_lognote(pricelist, po_line, changes, current_time) + + def _create_pricelist_lognote(self, pricelist, po_line, changes, timestamp): + message = f""" + System Fields Updated from PO
    + PO: {po_line.order_id.name}
    + Changes: +
      + {"".join(changes)} +
    • System Update: {timestamp}
    • +
    + Updated By: {self.env.user.name} + """ + + pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id) + class PurchaseOrderUnlockWizard(models.TransientModel): _name = 'purchase.order.unlock.wizard' _description = 'Wizard untuk memberikan alasan unlock PO' -- cgit v1.2.3 From 360c47384d876d17725be8ff3ca6b83a9078615b Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 13 May 2025 10:10:34 +0700 Subject: (andri) revisi current date pada log note purchase pricelist --- indoteknik_custom/models/purchase_order.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index a3e2c388..c5ba5792 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -562,7 +562,9 @@ class PurchaseOrder(models.Model): i = 0 for line in self.order_line: i += 1 - current_time = datetime.utcnow() + + utc_time = fields.Datetime.now() + current_time = utc_time.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') # print(i, len(self.order_line)) price_unit = line.price_unit @@ -635,7 +637,6 @@ class PurchaseOrder(models.Model): {"".join(changes)}
  • System Update: {current_time}
  • - Updated By: {self.env.user.name} """ purchase_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id) -- cgit v1.2.3 From f134f03f0e9998001a6fec44502a1e5a0a6b821b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 13 May 2025 11:13:36 +0700 Subject: fix bug return, and change view project --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index dd2365ec..6a6fe352 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -87,7 +87,7 @@ class StockPicking(models.Model): ) sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan", ) paket_documentation = fields.Binary(string="Dokumentasi Paket", ) - sj_return_date = fields.Datetime(string="SJ Return Date", ) + sj_return_date = fields.Datetime(string="SJ Return Date", copy=False) responsible = fields.Many2one('res.users', string='Responsible', tracking=True) approval_status = fields.Selection([ -- cgit v1.2.3 From 2c565f289de7d5827a7d9e09b4919ecffcf0437c Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 13 May 2025 13:19:34 +0700 Subject: (andri) add log note muncul di variant ketika ada perubahan di template --- indoteknik_custom/models/product_template.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 19de8bb7..1d4723a6 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -869,6 +869,11 @@ class ProductTemplate(models.Model): if changes: record.message_post(body=f"Updated:
      {''.join(changes)}
    ") + # log changes to product variants + variant_message = f"Updated:
      {''.join(changes)}
    " + for variant in record.product_variant_ids: + variant.message_post(body=variant_message) + # simpan data lama dan log perubahan field def write(self, vals): context = self._get_context_with_all_info(vals) -- cgit v1.2.3 From 2469ee37cfe854f0419a8c3fbabed5bc32bcaa6e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 13 May 2025 14:56:55 +0700 Subject: push --- indoteknik_custom/models/purchase_order_sales_match.py | 1 + indoteknik_custom/models/purchase_pricelist.py | 11 +++++++++++ 2 files changed, 12 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py index ed013dd5..0bd0092b 100644 --- a/indoteknik_custom/models/purchase_order_sales_match.py +++ b/indoteknik_custom/models/purchase_order_sales_match.py @@ -27,6 +27,7 @@ class PurchaseOrderSalesMatch(models.Model): purchase_price_so = fields.Float(string='Purchase Price Sale Order', related='sale_line_id.purchase_price') purchase_price_po = fields.Float('Purchase Price PO', compute='_compute_purchase_price_po') purchase_line_id = fields.Many2one('purchase.order.line', string='Purchase Line', compute='_compute_purchase_line_id') + hold_outgoing_so = fields.Boolean(string='Hold Outgoing SO', related='sale_id.hold_outgoing') def _compute_purchase_line_id(self): for line in self: diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py index e5b35d7f..dd680f4d 100755 --- a/indoteknik_custom/models/purchase_pricelist.py +++ b/indoteknik_custom/models/purchase_pricelist.py @@ -83,6 +83,15 @@ class PurchasePricelist(models.Model): massage="Ada duplikat product dan vendor, berikut data yang anda duplikat : \n" + str(existing_purchase.product_id.name) + " - " + str(existing_purchase.vendor_id.name) + " - " + str(existing_purchase.product_price) if existing_purchase: raise UserError(massage) + + def sync_pricelist_item_promo(self, product): + pricelist_product = self.env['product.pricelist.item'].search([('product_id', '=', product.id), ('pricelist_id', '=', 17022)]) + for pricelist in pricelist_product: + if pricelist.fixed_price == 0: + flashsale = self.env['product.pricelist.item'].search([('product_id', '=', product.id), ('pricelist_id.is_flash_sale', '=', True)]) + if flashsale: + flashsale.fixed_price = 0 + return def action_calculate_pricelist(self): MAX_PRICELIST = 10 @@ -94,6 +103,8 @@ class PurchasePricelist(models.Model): records = self.env['purchase.pricelist'].browse(active_ids) price_group = self.env['price.group'].collect_price_group() for rec in records: + if rec.include_price == 0: + rec.sync_pricelist_item_promo(rec.product_id) product_group = rec.product_id.product_tmpl_id.x_manufacture.pricing_group or None price_incl = rec.include_price -- cgit v1.2.3 From bf79c492c047b8b9e0aa7657959a6f94263765dd Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 14 May 2025 08:28:53 +0700 Subject: (andri) add log note di product ketika variant ada perubahan --- indoteknik_custom/models/product_template.py | 31 ++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 1d4723a6..2679fbfd 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -1230,6 +1230,8 @@ class ProductProduct(models.Model): 'product_template_image_ids': 'Extra Product Media', } + template_changes = {} + for record in self: changes = [] for field_name in vals: @@ -1288,8 +1290,33 @@ class ProductProduct(models.Model): changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") if changes: - message = "Updated:
      %s
    " % "".join(changes) - record.message_post(body=message) + # Post message to variant + variant_message = "Updated:
      %s
    " % "".join(changes) + record.message_post(body=variant_message) + + # Group changes by template for posting to template + template_id = record.product_tmpl_id.id + if template_id not in template_changes: + template_changes[template_id] = {} + + # Store variant information including ID for creating clickable link + template_changes[template_id][record.id] = { + 'name': record.display_name or f"Variant {record.id}", + 'changes': changes + } + + # Post grouped messages to templates with clickable links using your format + for template_id, variants_data in template_changes.items(): + template = self.env['product.template'].browse(template_id) + if template.exists(): + template_message = "Variant Updates:
    " + + for variant_id, variant_data in variants_data.items(): + # Create clickable link using your format + variant_link = f"{variant_data['name']}
    " + template_message += f"{variant_link}
      {''.join(variant_data['changes'])}

    " + + template.message_post(body=template_message) # simpan data lama dan log perubahan field def write(self, vals): -- cgit v1.2.3 From eeb873f69948315e214082315cfeedfe6c7536bf Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 14 May 2025 09:46:22 +0700 Subject: add notes in sales order --- indoteknik_custom/models/sale_order.py | 466 ++++++++++++++++++--------------- 1 file changed, 259 insertions(+), 207 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 0d4fc6c3..99aa8053 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -9,6 +9,7 @@ from collections import defaultdict _logger = logging.getLogger(__name__) + class CancelReasonOrder(models.TransientModel): _name = 'cancel.reason.order' _description = 'Wizard for Cancel Reason order' @@ -44,7 +45,7 @@ class CancelReasonOrder(models.TransientModel): raise UserError('Attachment bukti wajib disertakan') order.write({'attachment_bukti': self.attachment_bukti}) order.message_post(body='Attachment Bukti Cancel', - attachment_ids=[self.attachment_bukti.id]) + attachment_ids=[self.attachment_bukti.id]) if self.reason_cancel == 'ganti_quotation': if self.nomor_so_pengganti: order.write({'nomor_so_pengganti': self.nomor_so_pengganti}) @@ -53,7 +54,8 @@ class CancelReasonOrder(models.TransientModel): order.confirm_cancel_order() return {'type': 'ir.actions.act_window_close'} - + + class ShippingOption(models.Model): _name = "shipping.option" _description = "Shipping Option" @@ -64,6 +66,7 @@ class ShippingOption(models.Model): etd = fields.Char(string="Estimated Delivery Time") sale_order_id = fields.Many2one('sale.order', string="Sale Order", ondelete="cascade") + class SaleOrderLine(models.Model): _inherit = 'sale.order.line' @@ -73,7 +76,7 @@ class SaleOrderLine(models.Model): if line.order_id: now = fields.Datetime.now() - initial_reason="Product Rejected" + initial_reason = "Product Rejected" # Buat lognote untuk product yang di delete log_note = (f"
  • Product '{line.product_id.name}' rejected.
  • " @@ -113,10 +116,12 @@ class SaleOrderLine(models.Model): return result + class SaleOrder(models.Model): _inherit = "sale.order" - ongkir_ke_xpdc = fields.Float(string='Ongkir ke Ekspedisi', help='Biaya ongkir ekspedisi', copy=False, index=True, tracking=3) + ongkir_ke_xpdc = fields.Float(string='Ongkir ke Ekspedisi', help='Biaya ongkir ekspedisi', copy=False, index=True, + tracking=3) metode_kirim_ke_xpdc = fields.Selection([ ('indoteknik_deliv', 'Indoteknik Delivery'), @@ -127,22 +132,31 @@ class SaleOrder(models.Model): ('other', 'Other'), ], string='Metode Kirim Ke Ekspedisi', copy=False, index=True, tracking=3) + notes = fields.Text(string="Notes", tracking=3) koli_lines = fields.One2many('sales.order.koli', 'sale_order_id', string='Sales Order Koli', auto_join=True) fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2') fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment') reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines') - order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True) - total_margin = fields.Float('Total Margin', compute='_compute_total_margin', help="Total Margin in Sales Order Header") - total_before_margin = fields.Float('Total Before Margin', compute='_compute_total_before_margin', help="Total Margin in Sales Order Header") - total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', help="Total % Margin in Sales Order Header") - total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header", compute='_compute_total_margin_excl_third_party') + order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', + string='Purchase Match Lines', + states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, + copy=True) + total_margin = fields.Float('Total Margin', compute='_compute_total_margin', + help="Total Margin in Sales Order Header") + total_before_margin = fields.Float('Total Before Margin', compute='_compute_total_before_margin', + help="Total Margin in Sales Order Header") + total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', + help="Total % Margin in Sales Order Header") + total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header", + compute='_compute_total_margin_excl_third_party') approval_status = fields.Selection([ ('pengajuan1', 'Approval Manager'), ('pengajuan2', 'Approval Pimpinan'), ('approved', 'Approved'), ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3) carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method', tracking=3) - have_visit_service = fields.Boolean(string='Have Visit Service', compute='_have_visit_service', help='To compute is customer get visit service') + have_visit_service = fields.Boolean(string='Have Visit Service', compute='_have_visit_service', + help='To compute is customer get visit service') delivery_amt = fields.Float(string='Delivery Amt', copy=False) shipping_cost_covered = fields.Selection([ ('indoteknik', 'Indoteknik'), @@ -152,7 +166,8 @@ class SaleOrder(models.Model): ('indoteknik', 'Indoteknik'), ('customer', 'Customer') ], string='Shipping Paid by', help='Siapa yang talangin dulu Biaya ekspedisi-nya?', copy=False, tracking=3) - sales_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) + sales_tax_id = fields.Many2one('account.tax', string='Tax', + domain=['|', ('active', '=', False), ('active', '=', True)]) have_outstanding_invoice = fields.Boolean('Have Outstanding Invoice', compute='_have_outstanding_invoice') have_outstanding_picking = fields.Boolean('Have Outstanding Picking', compute='_have_outstanding_picking') have_outstanding_po = fields.Boolean('Have Outstanding PO', compute='_have_outstanding_po') @@ -173,9 +188,14 @@ class SaleOrder(models.Model): ('sebagian', 'Sebagian Diproses'), ('menunggu', 'Menunggu Diproses'), ], copy=False) - partner_purchase_order_name = fields.Char(string='Nama PO Customer', copy=False, help="Nama purchase order customer, diisi oleh customer melalui website.", tracking=3) - partner_purchase_order_description = fields.Text(string='Keterangan PO Customer', copy=False, help="Keterangan purchase order customer, diisi oleh customer melalui website.", tracking=3) - partner_purchase_order_file = fields.Binary(string='File PO Customer', copy=False, help="File purchase order customer, diisi oleh customer melalui website.") + partner_purchase_order_name = fields.Char(string='Nama PO Customer', copy=False, + help="Nama purchase order customer, diisi oleh customer melalui website.", + tracking=3) + partner_purchase_order_description = fields.Text(string='Keterangan PO Customer', copy=False, + help="Keterangan purchase order customer, diisi oleh customer melalui website.", + tracking=3) + partner_purchase_order_file = fields.Binary(string='File PO Customer', copy=False, + help="File purchase order customer, diisi oleh customer melalui website.") payment_status = fields.Selection([ ('pending', 'Pending'), ('capture', 'Capture'), @@ -189,17 +209,22 @@ class SaleOrder(models.Model): ('partial_refund', 'Partial Refund'), ('partial_chargeback', 'Partial Chargeback'), ('authorize', 'Authorize'), - ], tracking=True, string='Payment Status', help='Payment Gateway Status / Midtrans / Web, https://docs.midtrans.com/en/after-payment/status-cycle') - date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ yang terakhir, tidak berpengaruh ke Accounting") + ], tracking=True, string='Payment Status', + help='Payment Gateway Status / Midtrans / Web, https://docs.midtrans.com/en/after-payment/status-cycle') + date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', + help="Tanggal Kirim di cetakan SJ yang terakhir, tidak berpengaruh ke Accounting") payment_type = fields.Char(string='Payment Type', help='Jenis pembayaran dengan Midtrans') gross_amount = fields.Float(string='Gross Amount', help='Jumlah pembayaran yang dilakukan dengan Midtrans') notification = fields.Char(string='Notification', help='Dapat membantu error dari approval') delivery_service_type = fields.Char(string='Delivery Service Type', help='data dari rajaongkir') - grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery', compute='_compute_grand_total') - payment_link_midtrans = fields.Char(string='Payment Link', help='Url payment yg digenerate oleh midtrans, harap diserahkan ke customer agar dapat dilakukan pembayaran secara mandiri') + grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery', + compute='_compute_grand_total') + payment_link_midtrans = fields.Char(string='Payment Link', + help='Url payment yg digenerate oleh midtrans, harap diserahkan ke customer agar dapat dilakukan pembayaran secara mandiri') payment_qr_code = fields.Binary("Payment QR Code") - due_id = fields.Many2one('due.extension', string="Due Extension", readonly=True, tracking=True) - vendor_approval_id = fields.Many2many('vendor.approval', string="Vendor Approval", readonly=True, tracking=True, copy=False) + due_id = fields.Many2one('due.extension', string="Due Extension", readonly=True, tracking=True) + vendor_approval_id = fields.Many2many('vendor.approval', string="Vendor Approval", readonly=True, tracking=True, + copy=False) customer_type = fields.Selection([ ('pkp', 'PKP'), ('nonpkp', 'Non PKP') @@ -239,8 +264,10 @@ class SaleOrder(models.Model): use_button = fields.Boolean(string='Using Calculate Selling Price', copy=False) unreserve_id = fields.Many2one('stock.picking', 'Unreserve Picking') voucher_shipping_id = fields.Many2one(comodel_name='voucher', string='Voucher Shipping', copy=False) - margin_after_delivery_purchase = fields.Float(string='Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase') - percent_margin_after_delivery_purchase = fields.Float(string='% Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase') + margin_after_delivery_purchase = fields.Float(string='Margin After Delivery Purchase', + compute='_compute_margin_after_delivery_purchase') + percent_margin_after_delivery_purchase = fields.Float(string='% Margin After Delivery Purchase', + compute='_compute_margin_after_delivery_purchase') purchase_delivery_amt = fields.Float(string='Purchase Delivery Amount', compute='_compute_purchase_delivery_amount') type_promotion = fields.Char(string='Type Promotion', compute='_compute_type_promotion') partner_invoice_id = fields.Many2one( @@ -254,11 +281,11 @@ class SaleOrder(models.Model): 'res.partner', string='Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True) - + payment_term_id = fields.Many2one( 'account.payment.term', string='Payment Terms', check_company=True, # Unrequired company domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True) - + total_weight = fields.Float(string='Total Weight', compute='_compute_total_weight') pareto_status = fields.Selection([ ('PR', 'Pareto Repeating'), @@ -275,7 +302,7 @@ class SaleOrder(models.Model): copy=False ) shipping_method_picking = fields.Char(string='Shipping Method Picking', compute='_compute_shipping_method_picking') - + reason_cancel = fields.Selection([ ('harga_terlalu_mahal', 'Harga barang terlalu mahal'), ('harga_web_tidak_valid', 'Harga web tidak valid'), @@ -296,7 +323,8 @@ class SaleOrder(models.Model): string="Attachment Bukti Cancel", readonly=False, ) nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) - shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") + shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", + domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]") hold_outgoing = fields.Boolean('Hold Outgoing SO', tracking=3) state_ask_cancel = fields.Selection([ ('hold', 'Hold'), @@ -355,7 +383,7 @@ class SaleOrder(models.Model): tax_sets.add(tax_ids) if len(tax_sets) > 1: raise ValidationError("Semua produk dalam Sales Order harus memiliki kombinasi pajak yang sama.") - + # @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain') # def _check_total_margin_excl_third_party(self): # for rec in self: @@ -403,14 +431,14 @@ class SaleOrder(models.Model): def action_indoteknik_estimate_shipping(self): if not self.real_shipping_id.kota_id.is_jabodetabek: raise UserError('Estimasi ongkir hanya bisa dilakukan di kota Jabodetabek') - + total_weight = 0 missing_weight_products = [] for line in self.order_line: if line.weight > 0: total_weight += line.weight * line.product_uom_qty - line.product_id.weight = line.weight + line.product_id.weight = line.weight else: missing_weight_products.append(line.product_id.name) @@ -420,10 +448,10 @@ class SaleOrder(models.Model): if total_weight == 0: raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.") - + if total_weight < 10: total_weight = 10 - + self.delivery_amt = total_weight * 3000 shipping_option = self.env["shipping.option"].create({ @@ -456,7 +484,7 @@ class SaleOrder(models.Model): for line in self.order_line: if line.weight > 0: total_weight += line.weight * line.product_uom_qty - line.product_id.weight = line.weight + line.product_id.weight = line.weight else: missing_weight_products.append(line.product_id.name) @@ -481,11 +509,11 @@ class SaleOrder(models.Model): etd = cost_detail['cost'][0]['etd'] value = cost_detail['cost'][0]['value'] shipping_options.append((service, description, etd, value, courier['code'])) - + self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink() _logger.info(f"Shipping options: {shipping_options}") - + for service, description, etd, value, provider in shipping_options: self.env["shipping.option"].create({ "name": service, @@ -495,22 +523,20 @@ class SaleOrder(models.Model): "sale_order_id": self.id, }) - self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id _logger.info(f"Shipping option SO ID: {self.shipping_option_id}") self.message_post( body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
    Detail Lain:
    " - f"{'
    '.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", + f"{'
    '.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment" ) - + # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}
    Detail Lain:
    {'
    '.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment") else: raise UserError("Gagal mendapatkan estimasi ongkir.") - def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id): url = 'https://pro.rajaongkir.com/api/cost' @@ -532,7 +558,7 @@ class SaleOrder(models.Model): if response.status_code == 200: return response.json() return None - + def _normalize_city_name(self, city_name): city_name = city_name.lower() @@ -552,7 +578,7 @@ class SaleOrder(models.Model): } normalized_city_name = self._normalize_city_name(city_name) - + response = requests.get(url, headers=headers) if response.status_code == 200: city_data = response.json() @@ -566,7 +592,7 @@ class SaleOrder(models.Model): headers = { 'key': '9b1310f644056d84d60b0af6bb21611a', } - + response = requests.get(url, headers=headers) if response.status_code == 200: subdistrict_data = response.json() @@ -578,15 +604,15 @@ class SaleOrder(models.Model): return subdistrict['subdistrict_id'] return None - def _compute_type_promotion(self): for rec in self: promotion_types = [] for promotion in rec.order_promotion_ids: for line_program in promotion.program_line_id: if line_program.promotion_type: - promotion_types.append(dict(line_program._fields['promotion_type'].selection).get(line_program.promotion_type)) - + promotion_types.append( + dict(line_program._fields['promotion_type'].selection).get(line_program.promotion_type)) + rec.type_promotion = ', '.join(sorted(set(promotion_types))) def _compute_purchase_delivery_amount(self): @@ -610,11 +636,14 @@ class SaleOrder(models.Model): delivery_amt = order.delivery_amt else: delivery_amt = 0 - order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (order.amount_untaxed-delivery_amt-order.fee_third_party-order.biaya_lain_lain)) * 100, 2) + order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / ( + order.amount_untaxed - delivery_amt - order.fee_third_party - order.biaya_lain_lain)) * 100, 2) def _compute_date_kirim(self): for rec in self: - picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')], order='date_doc_kirim desc', limit=1) + picking = self.env['stock.picking'].search( + [('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')], + order='date_doc_kirim desc', limit=1) rec.date_kirim_ril = picking.date_doc_kirim rec.date_status_done = picking.date_done rec.date_driver_arrival = picking.driver_arrival_date @@ -626,54 +655,55 @@ class SaleOrder(models.Model): 'so_ids': [x.id for x in self] } return action - + def _compute_fullfillment(self): for rec in self: - # rec.fullfillment_line.unlink() - # - # for line in rec.order_line: - # line._compute_reserved_from() + # rec.fullfillment_line.unlink() + # + # for line in rec.order_line: + # line._compute_reserved_from() rec.compute_fullfillment = True @api.depends('date_order', 'estimated_arrival_days', 'state', 'estimated_arrival_days_start') def _compute_eta_date(self): - current_date = datetime.now().date() - for rec in self: - if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start: + current_date = datetime.now().date() + for rec in self: + if rec.date_order and rec.state not in [ + 'cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start: rec.eta_date = current_date + timedelta(days=rec.estimated_arrival_days) rec.eta_date_start = current_date + timedelta(days=rec.estimated_arrival_days_start) else: rec.eta_date = False rec.eta_date_start = False - - - def get_days_until_next_business_day(self,start_date=None, *args, **kwargs): + + def get_days_until_next_business_day(self, start_date=None, *args, **kwargs): today = start_date or datetime.today().date() offset = 0 # Counter jumlah hari yang ditambahkan holiday = self.env['hr.public.holiday'] - while True : + while True: today += timedelta(days=1) offset += 1 - + if today.weekday() >= 5: continue is_holiday = holiday.search([("start_date", "=", today)]) if is_holiday: continue - + break return offset - + def calculate_sla_by_vendor(self, products): product_ids = products.mapped('product_id.id') # Kumpulkan semua ID produk include_instant = True # Default True, tetapi bisa menjadi False # Cek apakah SEMUA produk memiliki qty_free_bandengan >= qty_needed - all_fast_products = all(product.product_id.qty_free_bandengan >= product.product_uom_qty for product in products) + all_fast_products = all( + product.product_id.qty_free_bandengan >= product.product_uom_qty for product in products) if all_fast_products: return {'slatime': 1, 'include_instant': include_instant} @@ -683,7 +713,7 @@ class SaleOrder(models.Model): ('is_winner', '=', True) ]) - max_slatime = 1 + max_slatime = 1 for vendor in vendors: vendor_sla = self.env['vendor.sla'].search([('id_vendor', '=', vendor.vendor_id.id)], limit=1) @@ -692,26 +722,26 @@ class SaleOrder(models.Model): if vendor_sla.unit == 'hari': vendor_duration = vendor_sla.duration * 24 * 60 include_instant = False - else : + else: vendor_duration = vendor_sla.duration * 60 include_instant = True - + estimation_sla = (1 * 24 * 60) + vendor_duration estimation_sla_days = estimation_sla / (24 * 60) slatime = math.ceil(estimation_sla_days) - + max_slatime = max(max_slatime, slatime) return {'slatime': max_slatime, 'include_instant': include_instant} - + def _calculate_etrts_date(self): for rec in self: if not rec.date_order: rec.expected_ready_to_ship = False return - + current_date = datetime.now().date() - + max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) @@ -719,29 +749,28 @@ class SaleOrder(models.Model): sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 if not rec.estimated_arrival_days: rec.estimated_arrival_days = sum_days - + eta_date = current_date + timedelta(days=sum_days) rec.commitment_date = eta_date rec.expected_ready_to_ship = eta_date - - @api.depends("order_line.product_id", "date_order") - def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date + + @api.depends("order_line.product_id", "date_order") + def _compute_etrts_date(self): # Function to calculate Estimated Ready To Ship Date self._calculate_etrts_date() - - + def _validate_expected_ready_ship_date(self): for rec in self: if rec.expected_ready_to_ship and rec.commitment_date: current_date = datetime.now().date() # Hanya membandingkan tanggal saja, tanpa jam expected_date = rec.expected_ready_to_ship.date() - + max_slatime = 1 # Default SLA jika tidak ada slatime = self.calculate_sla_by_vendor(rec.order_line) max_slatime = max(max_slatime, slatime['slatime']) sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1 eta_minimum = current_date + timedelta(days=sum_days) - + if expected_date < eta_minimum: rec.expected_ready_to_ship = eta_minimum raise ValidationError( @@ -750,16 +779,16 @@ class SaleOrder(models.Model): ) else: rec.commitment_date = rec.expected_ready_to_ship - - - @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship + + @api.onchange('expected_ready_to_ship') # Hangle Onchange form Expected Ready to Ship def _onchange_expected_ready_ship_date(self): self._validate_expected_ready_ship_date() def _set_etrts_date(self): for order in self: if order.state in ('done', 'cancel', 'sale'): - raise UserError(_("You cannot change the Estimated Ready To Ship Date on a done, sale or cancelled order.")) + raise UserError( + _("You cannot change the Estimated Ready To Ship Date on a done, sale or cancelled order.")) # order.move_lines.write({'estimated_ready_ship_date': order.estimated_ready_ship_date}) def _prepare_invoice(self): @@ -771,10 +800,12 @@ class SaleOrder(models.Model): self.ensure_one() journal = self.env['account.move'].with_context(default_move_type='out_invoice')._get_default_journal() if not journal: - raise UserError(_('Please define an accounting sales journal for the company %s (%s).') % (self.company_id.name, self.company_id.id)) + raise UserError( + _('Please define an accounting sales journal for the company %s (%s).') % (self.company_id.name, + self.company_id.id)) parent_id = self.partner_id.parent_id - parent_id = parent_id if parent_id else self.partner_id + parent_id = parent_id if parent_id else self.partner_id invoice_vals = { 'ref': self.client_order_ref or '', @@ -791,7 +822,8 @@ class SaleOrder(models.Model): 'partner_id': parent_id.id, 'partner_shipping_id': parent_id.id, 'real_invoice_id': self.real_invoice_id.id, - 'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position(self.partner_invoice_id.id)).id, + 'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position( + self.partner_invoice_id.id)).id, 'partner_bank_id': self.company_id.partner_id.bank_ids[:1].id, 'journal_id': journal.id, # company comes from the journal 'invoice_origin': self.name, @@ -807,10 +839,10 @@ class SaleOrder(models.Model): def _validate_email(self): rule_regex = self.env['ir.config_parameter'].sudo().get_param('sale.order.validate_email') or '' pattern = rf'^{rule_regex}$' - + if self.email and not re.match(pattern, self.email): raise UserError('Email yang anda input kurang valid') - + # @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered') def _validate_delivery_amt(self): is_indoteknik = self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik' @@ -822,13 +854,14 @@ class SaleOrder(models.Model): raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum diisi.') else: raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.') - + if self.delivery_amt < 100: if self.carrier_id.id == 1: - raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.') + raise UserError( + 'Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.') else: - raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.') - + raise UserError( + 'Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.') # if self.delivery_amt < 5000: # if (self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []): @@ -837,7 +870,6 @@ class SaleOrder(models.Model): # else: # raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi jumlah minimum.') - def override_allow_create_invoice(self): if not self.env.user.is_accounting: raise UserError('Hanya Finance Accounting yang dapat klik tombol ini') @@ -878,14 +910,14 @@ class SaleOrder(models.Model): 'sale_ids': [x.id for x in self] } return action - + def open_form_multi_update_state(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_quotation_so_multi_update') action['context'] = { 'quotation_ids': [x.id for x in self] } return action - + def action_multi_update_invoice_status(self): for sale in self: sale.update({ @@ -898,7 +930,7 @@ class SaleOrder(models.Model): for line in order.order_line: total += line.vendor_subtotal order.purchase_total = total - + def check_data_real_delivery_address(self): real_delivery_address = self.real_shipping_id @@ -918,8 +950,8 @@ class SaleOrder(models.Model): def generate_payment_link_midtrans_sales_order(self): # midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox # midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox - midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production - midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production + midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production + midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production so_number = self.name so_number = so_number.replace('/', '-') so_grandtotal = math.floor(self.grand_total) @@ -934,7 +966,8 @@ class SaleOrder(models.Model): if check_response.status_code == 200: status_response = check_response.json() - if status_response.get('transaction_status') == 'expire' or status_response.get('transaction_status') == 'cancel': + if status_response.get('transaction_status') == 'expire' or status_response.get( + 'transaction_status') == 'cancel': so_number = so_number + '-cpl' json_data = { @@ -982,8 +1015,7 @@ class SaleOrder(models.Model): if line.product_id.type == 'product': line_no += 1 line.line_no = line_no - - + def write(self, vals): if 'carrier_id' in vals: for picking in self.picking_ids: @@ -996,7 +1028,7 @@ class SaleOrder(models.Model): ('state', 'in', so_state), ('so_status', '!=', 'terproses'), ]) - + for sale in sales: picking_states = ['draft', 'assigned', 'confirmed', 'waiting'] have_outstanding_pick = any(x.state in picking_states for x in sale.picking_ids) @@ -1010,16 +1042,16 @@ class SaleOrder(models.Model): sale.so_status = 'terproses' else: sale.so_status = 'menunggu' - + for picking in sale.picking_ids: sum_qty_pick = sum(move_line.product_uom_qty for move_line in picking.move_ids_without_package) sum_qty_reserved = sum(move_line.product_uom_qty for move_line in picking.move_line_ids_without_package) if picking.state == 'done': continue - elif sum_qty_pick == sum_qty_reserved and not picking.date_reserved:# baru ke reserved + elif sum_qty_pick == sum_qty_reserved and not picking.date_reserved: # baru ke reserved current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') picking.date_reserved = current_time - elif sum_qty_pick == sum_qty_reserved:# sudah ada data reserved + elif sum_qty_pick == sum_qty_reserved: # sudah ada data reserved picking.date_reserved = picking.date_reserved else: picking.date_reserved = '' @@ -1054,7 +1086,7 @@ class SaleOrder(models.Model): @api.onchange('partner_id') def onchange_partner_contact(self): parent_id = self.partner_id.parent_id - parent_id = parent_id if parent_id else self.partner_id + parent_id = parent_id if parent_id else self.partner_id self.npwp = parent_id.npwp self.sppkp = parent_id.sppkp @@ -1062,13 +1094,13 @@ class SaleOrder(models.Model): self.email = parent_id.email self.pareto_status = parent_id.pareto_status self.user_id = parent_id.user_id - + @api.onchange('partner_id') def onchange_partner_id(self): # INHERIT result = super(SaleOrder, self).onchange_partner_id() parent_id = self.partner_id.parent_id - parent_id = parent_id if parent_id else self.partner_id + parent_id = parent_id if parent_id else self.partner_id self.partner_invoice_id = parent_id return result @@ -1101,16 +1133,16 @@ class SaleOrder(models.Model): minimum_amount = 20000000 for order in self: order.have_visit_service = self.amount_total > minimum_amount - + def _get_helper_ids(self): helper_ids_str = self.env['ir.config_parameter'].sudo().get_param('sale.order.user_helper_ids') return helper_ids_str.split(', ') - + def write(self, values): helper_ids = self._get_helper_ids() if str(self.env.user.id) in helper_ids: values['helper_by_id'] = self.env.user.id - + return super(SaleOrder, self).write(values) def check_due(self): @@ -1128,21 +1160,21 @@ class SaleOrder(models.Model): def _validate_order(self): if self.payment_term_id.id == 31 and self.total_percent_margin < 25: raise UserError("Jika ingin menggunakan Tempo 90 Hari maka margin harus di atas 25%") - - if self.warehouse_id.id != 8 and self.warehouse_id.id != 10: #GD Bandengan + + if self.warehouse_id.id != 8 and self.warehouse_id.id != 10: # GD Bandengan raise UserError('Gudang harus Bandengan') - + if self.state not in ['draft', 'sent']: raise UserError("Status harus draft atau sent") - + self._validate_npwp() - + def _validate_npwp(self): num_digits = sum(c.isdigit() for c in self.npwp) if num_digits < 10: raise UserError("NPWP harus memiliki minimal 10 digit") - + # pattern = r'^\d{10,}$' # return re.match(pattern, self.npwp) is not None @@ -1170,7 +1202,8 @@ class SaleOrder(models.Model): if (partner.customer_type == 'pkp' or order.customer_type == 'pkp') and order.sppkp != partner.sppkp: raise UserError("SPPKP berbeda pada Master Data Customer") if not order.client_order_ref and order.create_date > datetime(2024, 6, 27): - raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") + raise UserError( + "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") if not order.user_id.active: raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact") @@ -1178,20 +1211,23 @@ class SaleOrder(models.Model): for order in self: for line in order.order_line: if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False: - search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)],order='name desc') + search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)], + order='name desc') if search_bom: confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') if not confirmed_bom: - raise UserError("Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") + raise UserError( + "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") else: raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD") - + def check_duplicate_product(self): for order in self: for line in order.order_line: - search_product = self.env['sale.order.line'].search([('product_id', '=', line.product_id.id), ('order_id', '=', order.id)]) - if len(search_product) > 1: - raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name)) + search_product = self.env['sale.order.line'].search( + [('product_id', '=', line.product_id.id), ('order_id', '=', order.id)]) + if len(search_product) > 1: + raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name)) def sale_order_approve(self): self.check_duplicate_product() @@ -1214,11 +1250,13 @@ class SaleOrder(models.Model): SYSTEM_UID = 25 FROM_WEBSITE = order.create_uid.id == SYSTEM_UID - if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement','cust_director']: + if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement', + 'cust_director']: raise UserError("This order not yet approved by customer procurement or director") if not order.client_order_ref and order.create_date > datetime(2024, 6, 27): - raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") + raise UserError( + "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") if not order.commitment_date and order.create_date > datetime(2024, 9, 12): raise UserError("Expected Delivery Date kosong, wajib diisi") @@ -1227,7 +1265,8 @@ class SaleOrder(models.Model): UserError('Real Delivery Address harus di isi') if order.validate_partner_invoice_due(): - return self._create_notification_action('Notification','Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') + return self._create_notification_action('Notification', + 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') term_days = 0 for term_line in order.payment_term_id.line_ids: @@ -1245,13 +1284,15 @@ class SaleOrder(models.Model): if (partner.customer_type == 'pkp' or order.customer_type == 'pkp') and order.sppkp != partner.sppkp: raise UserError("SPPKP berbeda pada Master Data Customer") if not order.client_order_ref and order.create_date > datetime(2024, 6, 27): - raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") + raise UserError( + "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") if not order.user_id.active: raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact") - + if order.validate_partner_invoice_due(): - return self._create_notification_action('Notification', 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') - + return self._create_notification_action('Notification', + 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') + if order._requires_approval_margin_leader(): order.approval_status = 'pengajuan2' return self._create_approval_notification('Pimpinan') @@ -1261,9 +1302,9 @@ class SaleOrder(models.Model): self.check_limit_so_to_invoice() order.approval_status = 'pengajuan1' return self._create_approval_notification('Sales Manager') - + raise UserError("Bisa langsung Confirm") - + def send_notif_to_salesperson(self, cancel=False): if not cancel: @@ -1281,7 +1322,9 @@ class SaleOrder(models.Model): salesperson_data = {} for rec in grouping_so: if rec.user_id.id not in salesperson_data: - salesperson_data[rec.user_id.id] = {'name': rec.user_id.name, 'orders': [], 'total_amount': 0, 'sum_total_amount': 0, 'business_partner': '', 'site': ''} # Menetapkan nilai awal untuk 'site' + salesperson_data[rec.user_id.id] = {'name': rec.user_id.name, 'orders': [], 'total_amount': 0, + 'sum_total_amount': 0, 'business_partner': '', + 'site': ''} # Menetapkan nilai awal untuk 'site' if rec.picking_ids: if not any(picking.state in ['assigned', 'confirmed', 'waiting'] for picking in rec.picking_ids): continue @@ -1300,7 +1343,8 @@ class SaleOrder(models.Model): }) salesperson_data[rec.user_id.id]['sum_total_amount'] += order_total_amount salesperson_data[rec.user_id.id]['business_partner'] = grouping_so[0].partner_id.main_parent_id.name - salesperson_data[rec.user_id.id]['site'] = grouping_so[0].partner_id.site_id.name # Menambahkan nilai hanya jika ada + salesperson_data[rec.user_id.id]['site'] = grouping_so[ + 0].partner_id.site_id.name # Menambahkan nilai hanya jika ada # Kirim email untuk setiap salesperson for salesperson_id, data in salesperson_data.items(): @@ -1324,9 +1368,9 @@ class SaleOrder(models.Model): template = self.env.ref('indoteknik_custom.mail_template_sale_order_notification_to_salesperson') email_body = template.body_html.replace('${table_content}', table_content) email_body = email_body.replace('${salesperson_name}', data['name']) - email_body = email_body.replace('${sum_total_amount}', str(data['sum_total_amount'])) - email_body = email_body.replace('${site}', str(data['site'])) - email_body = email_body.replace('${business_partner}', str(data['business_partner'])) + email_body = email_body.replace('${sum_total_amount}', str(data['sum_total_amount'])) + email_body = email_body.replace('${site}', str(data['site'])) + email_body = email_body.replace('${business_partner}', str(data['business_partner'])) # Kirim email self.env['mail.mail'].create({ 'subject': 'Notification: Sale Orders', @@ -1362,8 +1406,9 @@ class SaleOrder(models.Model): if (outstanding_amount + rec.amount_total) >= block_stage: if block_stage != 0: remaining_credit_limit = block_stage - outstanding_amount - raise UserError(_("%s is in Blocking Stage, Remaining credit limit is %s, from %s and outstanding %s") - % (rec.partner_id.name, remaining_credit_limit, block_stage, outstanding_amount)) + raise UserError( + _("%s is in Blocking Stage, Remaining credit limit is %s, from %s and outstanding %s") + % (rec.partner_id.name, remaining_credit_limit, block_stage, outstanding_amount)) def check_limit_so_to_invoice(self): for rec in self: @@ -1385,7 +1430,8 @@ class SaleOrder(models.Model): # Validasi limit if remaining_credit_limit <= 0 and block_stage > 0 and not is_cbd: - raise UserError(_("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.") + raise UserError( + _("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.") % (rec.partner_id.name, block_stage - current_total, block_stage, outstanding_amount)) def validate_different_vendor(self): @@ -1395,11 +1441,11 @@ class SaleOrder(models.Model): if self.vendor_approval_id and all(v.state != 'draft' for v in self.vendor_approval_id): return False - + different_vendor = self.order_line.filtered( lambda l: l.vendor_id and l.vendor_md_id and l.vendor_id.id != l.vendor_md_id.id ) - + if different_vendor: vendor_approvals = [] for line in different_vendor: @@ -1425,15 +1471,14 @@ class SaleOrder(models.Model): if line.purchase_price_md else False ), }) - + vendor_approvals.append(vendor_approval.id) - + self.vendor_approval_id = [(4, vid) for vid in vendor_approvals] return True else: return False - def action_confirm(self): for order in self: order._validate_uniform_taxes() @@ -1443,38 +1488,41 @@ class SaleOrder(models.Model): order.check_limit_so_to_invoice() if self.validate_different_vendor() and not self.vendor_approval: return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor') - + order.check_data_real_delivery_address() order.sale_order_check_approve() order._validate_order() order.order_line.validate_line() - + main_parent = order.partner_id.get_main_parent() SYSTEM_UID = 25 FROM_WEBSITE = order.create_uid.id == SYSTEM_UID - - if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement', 'cust_director']: + + if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement', + 'cust_director']: raise UserError("This order not yet approved by customer procurement or director") if not order.client_order_ref and order.create_date > datetime(2024, 6, 27): - raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") + raise UserError( + "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") if not order.commitment_date and order.create_date > datetime(2024, 9, 12): raise UserError("Expected Delivery Date kosong, wajib diisi") - + if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - + if order.validate_partner_invoice_due(): - return self._create_notification_action('Notification', 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') - + return self._create_notification_action('Notification', + 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') + if order._requires_approval_margin_leader(): order.approval_status = 'pengajuan2' return self._create_approval_notification('Pimpinan') elif order._requires_approval_margin_manager(): order.approval_status = 'pengajuan1' return self._create_approval_notification('Sales Manager') - + order.approval_status = 'approved' order._set_sppkp_npwp_contact() order.calculate_line_no() @@ -1488,7 +1536,7 @@ class SaleOrder(models.Model): for line in order.order_line: if line.display_type == 'line_note': note.append(line.name) - + if order.picking_ids: # Sort picking_ids by creation date to get the most recent one latest_picking = order.picking_ids.sorted(key=lambda p: p.create_date, reverse=True)[0] @@ -1502,7 +1550,7 @@ class SaleOrder(models.Model): main_parent = self.partner_id.get_main_parent() if self._name != 'sale.order': return super(SaleOrder, self).action_cancel() - + if self.have_outstanding_invoice: raise UserError("Invoice harus di Cancel dahulu") @@ -1513,7 +1561,7 @@ class SaleOrder(models.Model): for line in self.order_line: if line.qty_delivered > 0: raise UserError("DO yang done harus di-Return oleh Logistik") - + if not self.web_approval: self.web_approval = 'company' # elif self.have_outstanding_po: @@ -1543,11 +1591,11 @@ class SaleOrder(models.Model): def validate_partner_invoice_due(self): parent_id = self.partner_id.parent_id.id - parent_id = parent_id if parent_id else self.partner_id.id + parent_id = parent_id if parent_id else self.partner_id.id if self.due_id and self.due_id.is_approve == False: raise UserError('Document Over Due Yang Anda Buat Belum Di Approve') - + query = [ ('partner_id', '=', parent_id), ('state', '=', 'posted'), @@ -1570,27 +1618,27 @@ class SaleOrder(models.Model): else: due_extension.unlink() return False - + def _requires_approval_margin_leader(self): return self.total_percent_margin <= 15 and not self.env.user.is_leader - + def _requires_approval_margin_manager(self): return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager - + def _create_approval_notification(self, approval_role): title = 'Warning' message = f'SO butuh approval {approval_role}' return self._create_notification_action(title, message) - + def _create_notification_action(self, title, message): return { 'type': 'ir.actions.client', 'tag': 'display_notification', - 'params': { 'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'} }, + 'params': {'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'}}, } - - def _set_sppkp_npwp_contact(self): + + def _set_sppkp_npwp_contact(self): partner = self.partner_id.parent_id or self.partner_id if not partner.sppkp: @@ -1609,7 +1657,7 @@ class SaleOrder(models.Model): # partner.npwp = self.npwp # partner.sppkp = self.sppkp # partner.email = self.email - + def _compute_total_margin(self): for order in self: total_margin = sum(line.item_margin for line in order.order_line if line.product_id) @@ -1617,7 +1665,7 @@ class SaleOrder(models.Model): total_margin -= order.ongkir_ke_xpdc order.total_margin = total_margin - + def _compute_total_before_margin(self): for order in self: total_before_margin = sum(line.item_before_margin for line in order.order_line if line.product_id) @@ -1632,9 +1680,10 @@ class SaleOrder(models.Model): delivery_amt = order.delivery_amt else: delivery_amt = 0 - + # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2) - order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-order.fee_third_party-order.biaya_lain_lain)) * 100, 2) + order.total_percent_margin = round( + (order.total_margin / (order.amount_untaxed - order.fee_third_party - order.biaya_lain_lain)) * 100, 2) # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2) @api.onchange('sales_tax_id') @@ -1657,20 +1706,20 @@ class SaleOrder(models.Model): voucher = self.voucher_id if voucher.limit > 0 and voucher.count_order >= voucher.limit: raise UserError('Voucher tidak dapat digunakan karena sudah habis digunakan') - + partner_voucher_orders = [] for order in voucher.order_ids: if order.partner_id.id == self.partner_id.id: partner_voucher_orders.append(order) - + if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user: raise UserError('Voucher tidak dapat digunakan karena Customer ini sudah menghabiskan kuota voucher') - + if self.pricelist_id.id in [x.id for x in voucher.excl_pricelist_ids]: raise UserError('Voucher tidak dapat digunakan karena pricelist ini tidak berlaku pada voucher') - + self.apply_voucher() - + def action_apply_voucher_shipping(self): for line in self.order_line: if line.order_promotion_id: @@ -1679,18 +1728,18 @@ class SaleOrder(models.Model): voucher = self.voucher_shipping_id if voucher.limit > 0 and voucher.count_order >= voucher.limit: raise UserError('Voucher tidak dapat digunakan karena sudah habis digunakan') - + partner_voucher_orders = [] for order in voucher.order_ids: if order.partner_id.id == self.partner_id.id: partner_voucher_orders.append(order) - + if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user: raise UserError('Voucher tidak dapat digunakan karena Customer ini sudah menghabiskan kuota voucher') - + if self.pricelist_id.id in [x.id for x in voucher.excl_pricelist_ids]: raise UserError('Voucher tidak dapat digunakan karena pricelist ini tidak berlaku pada voucher') - + self.apply_voucher_shipping() def apply_voucher(self): @@ -1707,7 +1756,7 @@ class SaleOrder(models.Model): for line in self.order_line: line.initial_discount = line.discount - + voucher_type = voucher['type'] used_total = voucher['total'][voucher_type] used_discount = voucher['discount'][voucher_type] @@ -1723,11 +1772,11 @@ class SaleOrder(models.Model): line_contribution = line.price_subtotal / used_total line_voucher = used_discount * line_contribution line_voucher_item = line_voucher / line.product_uom_qty - + line_price_unit = line.price_unit / 1.11 if any(tax.id == 23 for tax in line.tax_id) else line.price_unit line_discount_item = line_price_unit * line.discount / 100 + line_voucher_item line_voucher_item = line_discount_item / line_price_unit * 100 - + line.amount_voucher_disc = line_voucher line.discount = line_voucher_item @@ -1738,27 +1787,27 @@ class SaleOrder(models.Model): for order in self: delivery_amt = order.delivery_amt voucher = order.voucher_shipping_id - + if voucher: max_discount_amount = voucher.discount_amount voucher_type = voucher.discount_type - + if voucher_type == 'fixed_price': discount = max_discount_amount elif voucher_type == 'percentage': discount = delivery_amt * (max_discount_amount / 100) - + delivery_amt -= discount - + delivery_amt = max(delivery_amt, 0) - + order.delivery_amt = delivery_amt - + order.amount_voucher_shipping_disc = discount order.applied_voucher_shipping_id = order.voucher_id.id def cancel_voucher(self): - self.applied_voucher_id = False + self.applied_voucher_id = False self.amount_voucher_disc = 0 for line in self.order_line: line.amount_voucher_disc = 0 @@ -1767,17 +1816,18 @@ class SaleOrder(models.Model): def cancel_voucher_shipping(self): self.delivery_amt + self.amount_voucher_shipping_disc - self.applied_voucher_shipping_id = False + self.applied_voucher_shipping_id = False self.amount_voucher_shipping_disc = 0 def action_web_approve(self): if self.env.uid != self.partner_id.user_id.id: - raise UserError('You are not authorized to approve this order. Only %s can approve this order.' % self.partner_id.user_id.name) - + raise UserError( + 'You are not authorized to approve this order. Only %s can approve this order.' % self.partner_id.user_id.name) + self.web_approval = 'company' template = self.env.ref('indoteknik_custom.mail_template_sale_order_web_approve_notification') template.send_mail(self.id, force_send=True) - + return { 'type': 'ir.actions.client', 'tag': 'display_notification', @@ -1837,13 +1887,14 @@ class SaleOrder(models.Model): if last_so and rec_purchase_price != last_so.purchase_price: rec_taxes = self.env['account.tax'].search([('id', '=', rec_taxes_id)], limit=1) if rec_taxes.price_include: - selling_price = (rec_purchase_price / 1.11) / (1 - (last_so.item_percent_margin_without_deduction / 100)) + selling_price = (rec_purchase_price / 1.11) / ( + 1 - (last_so.item_percent_margin_without_deduction / 100)) else: selling_price = rec_purchase_price / (1 - (last_so.item_percent_margin_without_deduction / 100)) tax_id = last_so.tax_id for tax in tax_id: if tax.price_include: - selling_price = selling_price + (selling_price*11/100) + selling_price = selling_price + (selling_price * 11 / 100) else: selling_price = selling_price discount = 0 @@ -1859,13 +1910,14 @@ class SaleOrder(models.Model): elif last_so and rec_vendor_id == order_line.vendor_id.id and rec_purchase_price != last_so.purchase_price: rec_taxes = self.env['account.tax'].search([('id', '=', rec_taxes_id)], limit=1) if rec_taxes.price_include: - selling_price = (rec_purchase_price / 1.11) / (1 - (last_so.item_percent_margin_without_deduction / 100)) + selling_price = (rec_purchase_price / 1.11) / ( + 1 - (last_so.item_percent_margin_without_deduction / 100)) else: selling_price = rec_purchase_price / (1 - (last_so.item_percent_margin_without_deduction / 100)) tax_id = last_so.tax_id for tax in tax_id: if tax.price_include: - selling_price = selling_price + (selling_price*11/100) + selling_price = selling_price + (selling_price * 11 / 100) else: selling_price = selling_price discount = 0 @@ -1896,14 +1948,14 @@ class SaleOrder(models.Model): return order # def write(self, vals): - # Call the super method to handle the write operation - # res = super(SaleOrder, self).write(vals) - # self._compute_etrts_date() - # Check if the update is coming from a save operation - # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']): - # self._update_partner_details() + # Call the super method to handle the write operation + # res = super(SaleOrder, self).write(vals) + # self._compute_etrts_date() + # Check if the update is coming from a save operation + # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']): + # self._update_partner_details() - # return res + # return res def _update_partner_details(self): for order in self: @@ -1932,11 +1984,11 @@ class SaleOrder(models.Model): if command[0] == 0: # A new line is being added raise UserError( "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") - + res = super(SaleOrder, self).write(vals) # self._check_total_margin_excl_third_party() if any(fields in vals for fields in ['delivery_amt', 'carrier_id', 'shipping_cost_covered']): self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): - self._calculate_etrts_date() - return res \ No newline at end of file + self._calculate_etrts_date() + return res -- cgit v1.2.3 From 509a771b8ba6122cc308323dbe90ff4a35e4fc72 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 14 May 2025 10:21:06 +0700 Subject: add multiparts product to barcoding product --- indoteknik_custom/models/barcoding_product.py | 34 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index 204c6128..5f4e80ea 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -12,7 +12,7 @@ class BarcodingProduct(models.Model): barcoding_product_line = fields.One2many('barcoding.product.line', 'barcoding_product_id', string='Barcoding Product Lines', auto_join=True) product_id = fields.Many2one('product.product', string="Product", tracking=3) quantity = fields.Float(string="Quantity", tracking=3) - type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product')], string='Type', default='print') + type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product'), ('multiparts', 'Multiparts Product')], string='Type', default='print') barcode = fields.Char(string="Barcode") qty_pcs_box = fields.Char(string="Quantity Pcs Box") @@ -40,16 +40,33 @@ class BarcodingProduct(models.Model): @api.onchange('product_id', 'quantity') def _onchange_product_or_quantity(self): - """Update barcoding_product_line based on product_id and quantity""" if self.product_id and self.quantity > 0: # Clear existing lines self.barcoding_product_line = [(5, 0, 0)] # Add a new line with the current product and quantity - self.barcoding_product_line = [(0, 0, { - 'product_id': self.product_id.id, - 'barcoding_product_id': self.id, - }) for _ in range(int(self.quantity))] + lines = [] + for i in range(int(self.quantity)): + lines.append((0, 0, { + 'product_id': self.product_id.id, + 'barcoding_product_id': self.id, + 'sequence_with_total': f"{i+1}/{int(self.quantity)}" + })) + self.barcoding_product_line = lines + + def write(self, vals): + """Override write to update sequence_with_total when quantity changes""" + res = super().write(vals) + if 'quantity' in vals and self.type == 'multiparts': + self._update_sequence_with_total() + return res + + def _update_sequence_with_total(self): + """Update sequence_with_total for all lines""" + for rec in self: + total = int(rec.quantity) + for index, line in enumerate(rec.barcoding_product_line, start=1): + line.sequence_with_total = f"{index}/{total}" class BarcodingProductLine(models.Model): @@ -59,4 +76,7 @@ class BarcodingProductLine(models.Model): barcoding_product_id = fields.Many2one('barcoding.product', string='Barcoding Product Ref', required=True, ondelete='cascade', index=True, copy=False) product_id = fields.Many2one('product.product', string="Product") - qr_code_variant = fields.Binary("QR Code Variant", related='product_id.qr_code_variant') \ No newline at end of file + qr_code_variant = fields.Binary("QR Code Variant", related='product_id.qr_code_variant') + sequence_with_total = fields.Char( + string="Sequence" + ) \ No newline at end of file -- cgit v1.2.3 From c5506e8e7c22b02968d4a8ae56b7147ee85e686a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 14 May 2025 11:24:04 +0700 Subject: fix bug --- indoteknik_custom/models/barcoding_product.py | 4 ---- indoteknik_custom/models/product_template.py | 9 +++++---- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index 5f4e80ea..335b481a 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -41,10 +41,8 @@ class BarcodingProduct(models.Model): @api.onchange('product_id', 'quantity') def _onchange_product_or_quantity(self): if self.product_id and self.quantity > 0: - # Clear existing lines self.barcoding_product_line = [(5, 0, 0)] - # Add a new line with the current product and quantity lines = [] for i in range(int(self.quantity)): lines.append((0, 0, { @@ -55,14 +53,12 @@ class BarcodingProduct(models.Model): self.barcoding_product_line = lines def write(self, vals): - """Override write to update sequence_with_total when quantity changes""" res = super().write(vals) if 'quantity' in vals and self.type == 'multiparts': self._update_sequence_with_total() return res def _update_sequence_with_total(self): - """Update sequence_with_total for all lines""" for rec in self: total = int(rec.quantity) for index, line in enumerate(rec.barcoding_product_line, start=1): diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 2679fbfd..03c7ced4 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -1320,10 +1320,11 @@ class ProductProduct(models.Model): # simpan data lama dan log perubahan field def write(self, vals): - old_values = self._collect_old_values(vals) - result = super().write(vals) - self._log_field_changes_product_variants(vals, old_values) - return result + if self.default_code in vals: + old_values = self._collect_old_values(vals) + result = super().write(vals) + self._log_field_changes_product_variants(vals, old_values) + return result class OutstandingMove(models.Model): _name = 'v.move.outstanding' -- cgit v1.2.3 From 2217e86706fcaf03da2c05761b4f837e90374ec2 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 14 May 2025 11:37:01 +0700 Subject: fix bug --- indoteknik_custom/models/product_template.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 03c7ced4..17805c6c 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -1320,11 +1320,19 @@ class ProductProduct(models.Model): # simpan data lama dan log perubahan field def write(self, vals): - if self.default_code in vals: + tracked_fields = [ + 'default_code', 'name', 'weight', 'x_manufacture', + 'public_categ_ids', 'search_rank', 'search_rank_weekly', + 'image_1920', 'unpublished', 'image_carousel_lines' + ] + + if any(field in vals for field in tracked_fields): old_values = self._collect_old_values(vals) result = super().write(vals) self._log_field_changes_product_variants(vals, old_values) return result + else: + return super().write(vals) class OutstandingMove(models.Model): _name = 'v.move.outstanding' -- cgit v1.2.3 From 1d7a2902b1b97e6fb7951252cf851695d6a8ee8e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 14 May 2025 15:09:46 +0700 Subject: shipment group --- .../models/account_move_due_extension.py | 2 +- indoteknik_custom/models/sale_order.py | 47 +++++++++++----------- indoteknik_custom/models/shipment_group.py | 3 ++ indoteknik_custom/models/stock_picking.py | 2 +- 4 files changed, 29 insertions(+), 25 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py index c48c2372..4a3f40e2 100644 --- a/indoteknik_custom/models/account_move_due_extension.py +++ b/indoteknik_custom/models/account_move_due_extension.py @@ -96,7 +96,7 @@ class DueExtension(models.Model): sales = self.env['sale.order'].browse(self.order_id.id) - sales.action_confirm() + sales.with_context({'due_approve': True}).action_confirm() self.order_id.due_id = self.id self.approve_by = self.env.user.id self.date_approve = datetime.utcnow() diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 99aa8053..8aff6b3a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1264,9 +1264,10 @@ class SaleOrder(models.Model): if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - if order.validate_partner_invoice_due(): - return self._create_notification_action('Notification', - 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') + if self.env.context.get('due_approve', []) == False: + if order.validate_partner_invoice_due(): + return self._create_notification_action('Notification', + 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') term_days = 0 for term_line in order.payment_term_id.line_ids: @@ -1289,9 +1290,9 @@ class SaleOrder(models.Model): if not order.user_id.active: raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact") - if order.validate_partner_invoice_due(): - return self._create_notification_action('Notification', - 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') + # if order.validate_partner_invoice_due(): + # return self._create_notification_action('Notification', + # 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') if order._requires_approval_margin_leader(): order.approval_status = 'pengajuan2' @@ -1511,10 +1512,11 @@ class SaleOrder(models.Model): if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - - if order.validate_partner_invoice_due(): - return self._create_notification_action('Notification', - 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') + + if self.env.context.get('due_approve', []) == False: + if order.validate_partner_invoice_due(): + return self._create_notification_action('Notification', + 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') if order._requires_approval_margin_leader(): order.approval_status = 'pengajuan2' @@ -1605,19 +1607,18 @@ class SaleOrder(models.Model): invoices = self.env['account.move'].search(query, order='invoice_date') if invoices: - if not self.env.user.is_leader and not self.env.user.is_sales_manager: - due_extension = self.env['due.extension'].create([{ - 'partner_id': parent_id, - 'day_extension': '3', - 'order_id': self.id, - }]) - due_extension.generate_due_line() - self.due_id = due_extension.id - if len(self.due_id.due_line) > 0: - return True - else: - due_extension.unlink() - return False + due_extension = self.env['due.extension'].create([{ + 'partner_id': parent_id, + 'day_extension': '3', + 'order_id': self.id, + }]) + due_extension.generate_due_line() + self.due_id = due_extension.id + if len(self.due_id.due_line) > 0: + return True + else: + due_extension.unlink() + return False def _requires_approval_margin_leader(self): return self.total_percent_margin <= 15 and not self.env.user.is_leader diff --git a/indoteknik_custom/models/shipment_group.py b/indoteknik_custom/models/shipment_group.py index b7d7ac12..a4bea9c4 100644 --- a/indoteknik_custom/models/shipment_group.py +++ b/indoteknik_custom/models/shipment_group.py @@ -93,6 +93,9 @@ class ShipmentGroupLine(models.Model): if self.shipment_id.carrier_id and self.shipment_id.carrier_id != picking.carrier_id: raise UserError('carrier must be same as shipment group') + if picking.total_mapping_koli == 0: + raise UserError(f'Picking {picking.name} tidak memiliki mapping koli') + self.partner_id = picking.partner_id self.shipping_paid_by = picking.sale_id.shipping_paid_by self.carrier_id = picking.carrier_id.id diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6a6fe352..ac812a32 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -280,7 +280,7 @@ class StockPicking(models.Model): state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') - last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') + last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim', copy=False) update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') def _get_kgx_awb_number(self): -- cgit v1.2.3 From 14b8ea0d6a3c091d63c10768fe61a02eeb55d49d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 14 May 2025 16:59:54 +0700 Subject: unlock validasi --- indoteknik_custom/models/sale_order.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8aff6b3a..366fcc6a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1213,13 +1213,13 @@ class SaleOrder(models.Model): if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False: search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)], order='name desc') - if search_bom: - confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') - if not confirmed_bom: - raise UserError( - "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") - else: - raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD") + # if search_bom: + # confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') + # if not confirmed_bom: + # raise UserError( + # "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") + # else: + # raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD") def check_duplicate_product(self): for order in self: @@ -1512,7 +1512,7 @@ class SaleOrder(models.Model): if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - + if self.env.context.get('due_approve', []) == False: if order.validate_partner_invoice_due(): return self._create_notification_action('Notification', -- cgit v1.2.3 From 41a69ecfe8513a608c5a0accfb253ba6fb06f60a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 14 May 2025 17:01:18 +0700 Subject: lock validasi bu mo --- indoteknik_custom/models/sale_order.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 366fcc6a..8aff6b3a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1213,13 +1213,13 @@ class SaleOrder(models.Model): if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False: search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)], order='name desc') - # if search_bom: - # confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') - # if not confirmed_bom: - # raise UserError( - # "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") - # else: - # raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD") + if search_bom: + confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') + if not confirmed_bom: + raise UserError( + "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") + else: + raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD") def check_duplicate_product(self): for order in self: @@ -1512,7 +1512,7 @@ class SaleOrder(models.Model): if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - + if self.env.context.get('due_approve', []) == False: if order.validate_partner_invoice_due(): return self._create_notification_action('Notification', -- cgit v1.2.3 From d70d65c19dccb2cc4264385c121c697ecd3fa8cd Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 14 May 2025 17:03:55 +0700 Subject: add validation state so on mrp production --- indoteknik_custom/models/mrp_production.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 8179fe56..58c2512c 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -244,6 +244,10 @@ class CheckBomProduct(models.Model): @api.constrains('production_id', 'product_id') def _check_product_bom_validation(self): for record in self: + if record.production_id.sale_order.state not in ['sale', 'done']: + raise UserError(( + "SO harus diconfirm terlebih dahulu." + )) if not record.production_id or not record.product_id: continue -- cgit v1.2.3 From 00cc0e587ad443fc5b3003ce80fdc1eef96ebb7b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 14 May 2025 17:09:32 +0700 Subject: push --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8aff6b3a..6550b859 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1624,7 +1624,7 @@ class SaleOrder(models.Model): return self.total_percent_margin <= 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader + return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.id == 375 and not self.env.user.is_leader # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager def _create_approval_notification(self, approval_role): -- cgit v1.2.3 From c40dc2dafb93759c9fdcda8581eb0c402d304886 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 15 May 2025 09:13:00 +0700 Subject: fix date approval invoice date --- indoteknik_custom/models/approval_invoice_date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/approval_invoice_date.py b/indoteknik_custom/models/approval_invoice_date.py index 48546e55..b58d55d4 100644 --- a/indoteknik_custom/models/approval_invoice_date.py +++ b/indoteknik_custom/models/approval_invoice_date.py @@ -31,7 +31,7 @@ class ApprovalInvoiceDate(models.Model): def button_approve(self): if not self.env.user.is_accounting: raise UserError("Hanya Accounting Yang Bisa Approve") - self.move_id.invoice_date = self.date_doc_do + self.move_id.invoice_date = self.date_doc_do.date() self.state = 'done' self.approve_date = datetime.utcnow() self.approve_by = self.env.user.id -- cgit v1.2.3 From 562f34bf5eb4b216ddf845813e405704f3842789 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 15 May 2025 11:39:09 +0700 Subject: disable reserve --- indoteknik_custom/models/stock_picking.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ac812a32..1c4a6cbc 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1012,6 +1012,8 @@ class StockPicking(models.Model): self.sale_id.date_doc_kirim = self.date_doc_kirim def action_assign(self): + if self.sale_id.hold_outgoing == True and self.location_id == 57 and self.location_dest_id == 60: + raise UserError("SO on hold") res = super(StockPicking, self).action_assign() current_time = datetime.datetime.utcnow() self.real_shipping_id = self.sale_id.real_shipping_id -- cgit v1.2.3 From aac9e231854f3f7ad112fc57ef53d2edac662c7a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 15 May 2025 11:43:42 +0700 Subject: disable reserve --- indoteknik_custom/models/sale_order.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 6550b859..1242108e 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -372,6 +372,9 @@ class SaleOrder(models.Model): if self.hold_outgoing == True: self.hold_outgoing = False else: + pick = self.env['stock.picking'].search([('sale_id', '=', self.id), ('state', 'not in', ['cancel','done']),('BU/PICK/', 'in', self.name)]) + for picking in pick: + picking.do_unreserve() self.hold_outgoing = True def _validate_uniform_taxes(self): -- cgit v1.2.3 From 3c170c77e4913313ca28169172dbad8c8726ad5c Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 15 May 2025 13:19:22 +0700 Subject: (andri) add button sync price to SO & penyesuaian readonly pada orderline PO --- indoteknik_custom/models/purchase_order.py | 228 ++++++++++++++++++++++++ indoteknik_custom/models/purchase_order_line.py | 54 ++++++ 2 files changed, 282 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 98b367d0..d5c08660 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -2,6 +2,7 @@ from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from dateutil.relativedelta import relativedelta from datetime import datetime, timedelta +from odoo.tools import float_compare import logging from pytz import timezone, utc import io @@ -89,6 +90,233 @@ class PurchaseOrder(models.Model): store_name = fields.Char(string='Nama Toko') purchase_order_count = fields.Integer('Purchase Order Count', related='partner_id.purchase_order_count') + + + def sync_price_to_so(self): + updated_lines = [] + skipped_lines = [] # Untuk melacak line yang dilewati karena harga sudah sama + affected_so_ids = set() # Untuk melacak SO mana saja yang terkena dampak + + for order in self: + # Filter hanya line-line yang ditandai + marked_lines = order.order_line.filtered(lambda l: l.mark_po_line) + + if not marked_lines: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('Info'), + 'message': _('Tidak ada item yang ditandai untuk disinkronkan'), + 'sticky': False, + 'type': 'info', + } + } + + # Cek apakah ada referensi ke sale order + if not order.sale_order_id and not order.order_sales_match_line: + raise UserError(_("Tidak ada Sales Order yang terkait dengan Purchase Order ini!")) + + # Jika PO dibuat dari SO langsung + if order.sale_order_id: + affected_so_ids.add(order.sale_order_id.id) + for po_line in marked_lines: + # Cari SO line yang terkait + so_line = po_line.so_line_id + if not so_line and po_line.product_id: + # Coba cari berdasarkan product jika tidak ada referensi langsung + so_line = self.env['sale.order.line'].search([ + ('product_id', '=', po_line.product_id.id), + ('order_id', '=', order.sale_order_id.id) + ], limit=1) + + if so_line: + old_price = so_line.purchase_price + + # Cek apakah harga sudah sama + if float_compare(old_price, po_line.price_unit, precision_digits=2) == 0: + skipped_lines.append({ + 'product': po_line.product_id.display_name, + 'so_name': so_line.order_id.name, + 'so_id': so_line.order_id.id, + 'price': old_price, + 'currency': order.currency_id or so_line.order_id.currency_id + }) + else: + so_line.purchase_price = po_line.price_unit + updated_lines.append({ + 'product': po_line.product_id.display_name, + 'so_line_id': so_line.id, + 'so_name': so_line.order_id.name, + 'so_id': so_line.order_id.id, + 'old_price': old_price, + 'new_price': po_line.price_unit, + 'currency': order.currency_id or so_line.order_id.currency_id + }) + + # Hapus tanda setelah sinkronisasi + po_line.mark_po_line = False + + # Jika PO terkait dengan beberapa SO (melalui order_sales_match_line) + elif order.order_sales_match_line: + for po_line in marked_lines: + # Cari match lines yang sesuai dengan product di PO line + match_lines = order.order_sales_match_line.filtered( + lambda m: m.product_id.id == po_line.product_id.id + ) + + for match_line in match_lines: + if match_line.sale_id: + affected_so_ids.add(match_line.sale_id.id) + + so_line = match_line.sale_line_id + if so_line: + old_price = so_line.purchase_price + + # Cek apakah harga sudah sama + if float_compare(old_price, po_line.price_unit, precision_digits=2) == 0: + skipped_lines.append({ + 'product': po_line.product_id.display_name, + 'so_name': so_line.order_id.name, + 'so_id': so_line.order_id.id, + 'price': old_price, + 'currency': order.currency_id or so_line.order_id.currency_id + }) + else: + so_line.purchase_price = po_line.price_unit + updated_lines.append({ + 'product': po_line.product_id.display_name, + 'so_line_id': so_line.id, + 'so_name': so_line.order_id.name, + 'so_id': so_line.order_id.id, + 'old_price': old_price, + 'new_price': po_line.price_unit, + 'currency': order.currency_id or so_line.order_id.currency_id + }) + + # Hapus tanda setelah sinkronisasi + po_line.mark_po_line = False + + # Ambil data SO yang terkena dampak + affected_sales_orders = self.env['sale.order'].browse(list(affected_so_ids)) + + # Buat pesan untuk log + message_body = "" + + # Jika ada line yang diupdate + if updated_lines: + # Kelompokkan perubahan berdasarkan SO + changes_by_so = {} + for line in updated_lines: + so_id = line['so_id'] + if so_id not in changes_by_so: + changes_by_so[so_id] = [] + changes_by_so[so_id].append(line) + + # Buat pesan untuk line yang diupdate + message_body += f"

    Harga Purchase pada Sales Order telah diperbarui dari {self.name}:

    " + + for so_id, lines in changes_by_so.items(): + so = self.env['sale.order'].browse(so_id) + # Buat link ke SO yang bisa diklik + message_body += f"

    Sales Order: {so.name}

      " + for line in lines: + # Format harga dalam format mata uang + currency = line['currency'] + old_price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['old_price'], {'display_currency': currency}) + new_price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['new_price'], {'display_currency': currency}) + + message_body += f"
    • {line['product']}:
      {old_price_formatted} → {new_price_formatted}
    • " + message_body += "
    " + + # Jika ada line yang dilewati karena harga sudah sama + if skipped_lines: + # Kelompokkan berdasarkan SO + skipped_by_so = {} + for line in skipped_lines: + so_id = line['so_id'] + if so_id not in skipped_by_so: + skipped_by_so[so_id] = [] + skipped_by_so[so_id].append(line) + + # Tambahkan pesan untuk line yang dilewati + if message_body: + message_body += "

    Item berikut dilewati karena harga sudah sama:

    " + else: + message_body += f"

    Tidak ada perubahan harga untuk PO {self.name}:

    " + message_body += "

    Item berikut sudah memiliki harga yang sama di SO:

    " + + for so_id, lines in skipped_by_so.items(): + so = self.env['sale.order'].browse(so_id) + message_body += f"

    Sales Order: {so.name}

      " + for line in lines: + # Format harga dalam format mata uang + currency = line['currency'] + price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['price'], {'display_currency': currency}) + + message_body += f"
    • {line['product']}: {price_formatted}
    • " + message_body += "
    " + + # Posting log message jika ada isi + if message_body: + subject = "Price Sync to SO" if updated_lines else "Price Sync - Harga Sama" + self.message_post(body=message_body, subject=subject) + + # Update juga log di setiap SO yang terkena dampak + if updated_lines: + for so in affected_sales_orders: + so_lines = [line for line in updated_lines if line['so_id'] == so.id] + if so_lines: + # Buat link ke PO yang bisa diklik + so_message = f"

    Harga Purchase diperbarui dari {self.name}:

      " + for line in so_lines: + # Format harga dalam format mata uang + currency = line['currency'] + old_price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['old_price'], {'display_currency': currency}) + new_price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['new_price'], {'display_currency': currency}) + + so_message += f"
    • {line['product']}:
      {old_price_formatted} → {new_price_formatted}
    • " + so_message += "
    " + so.message_post(body=so_message, subject=f"Price Updated from PO {self.name}") + + # Recalculate margins + if updated_lines and hasattr(self, 'compute_total_margin'): + self.compute_total_margin() + + # Recalculate margins di SO juga + if updated_lines: + for so in affected_sales_orders: + if hasattr(so, 'compute_total_margin'): + so.compute_total_margin() + + # Tentukan pesan notifikasi dan tipe + if updated_lines and skipped_lines: + message = f"{len(updated_lines)} item diperbarui dan {len(skipped_lines)} item dilewati karena harga sudah sama" + title = "Sukses!" + notification_type = "success" + elif updated_lines: + message = f"{len(updated_lines)} item telah diperbarui harganya di {len(affected_so_ids)} Sales Order" + title = "Sukses!" + notification_type = "success" + elif skipped_lines: + message = f"item tersebut ({len(skipped_lines)}) sudah memiliki harga yang sama" + title = "Info" + notification_type = "info" + else: + message = "Tidak ada line yang berhasil diperbarui" + title = "Info" + notification_type = "info" + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _(title), + 'message': _(message), + 'sticky': False, + 'type': notification_type, + } + } # cek payment term def _check_payment_term(self): _logger.info("Check Payment Term Terpanggil") diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py index 033469b8..766e304b 100755 --- a/indoteknik_custom/models/purchase_order_line.py +++ b/indoteknik_custom/models/purchase_order_line.py @@ -51,6 +51,8 @@ class PurchaseOrderLine(models.Model): contribution_cost_service = fields.Float(string='Contribution Cost Service', compute='_compute_doc_delivery_amt') ending_price = fields.Float(string='Ending Price', compute='_compute_doc_delivery_amt') + mark_po_line = fields.Boolean(string=' ', default=False, help='centang jika anda ingin menandai PO line ini') + def _compute_doc_delivery_amt(self): for line in self: # Inisialisasi nilai default untuk field computed @@ -385,3 +387,55 @@ class PurchaseOrderLine(models.Model): line.delivery_amt_line = delivery_amt * contribution else: line.delivery_amt_line = 0 + + # @api.model + # def create(self, vals): + # """ + # Override method create untuk mencegah penambahan line baru + # jika status PO sudah bukan draft + # """ + # # Cek apakah ada order_id di vals + # if vals.get('order_id'): + # order = self.env['purchase.order'].browse(vals['order_id']) + # # Jika status PO bukan draft, tolak pembuatan line baru + # if order.state in ['purchase', 'done', 'cancel']: + # raise UserError(_("Tidak bisa menambahkan item baru karena PO sudah dikonfirmasi atau selesai.")) + + # return super(PurchaseOrderLine, self).create(vals) + + # def write(self, vals): + # """ + # Override method write untuk membatasi field yang bisa diubah + # jika status PO sudah bukan draft + # """ + # for line in self: + # if line.order_id.state in ['purchase', 'done', 'cancel']: + # # Hanya izinkan mengubah mark_po_line + # if 'mark_po_line' in vals and len(vals) == 1: + # # Izinkan perubahan jika hanya field mark_po_line yang diubah + # return super(PurchaseOrderLine, self).write(vals) + # else: + # _logger.info("Fields being updated: %s", vals.keys()) + # raise UserError(_("Tidak bisa mengubah item karena PO sudah dikonfirmasi atau selesai.")) + + # return super(PurchaseOrderLine, self).write(vals) + + # def toggle_mark_po_line(self): + # """ + # Method khusus untuk toggle mark_po_line, aman digunakan dalam semua status PO + # """ + # for line in self: + # line.mark_po_line = not line.mark_po_line + # return True + + # def unlink(self): + # """ + # Override method unlink untuk mencegah penghapusan line + # jika status PO sudah bukan draft + # """ + # for line in self: + # if line.order_id.state in ['purchase', 'done', 'cancel']: + # raise UserError(_("Tidak bisa menghapus item karena PO sudah dikonfirmasi atau selesai.")) + + # return super(PurchaseOrderLine, self).unlink() + -- cgit v1.2.3 From 351934c0b2107782557998feb68c71cc5ba024e0 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 15 May 2025 13:21:53 +0700 Subject: (andri) hapus method yang tidak diperlukan/comment --- indoteknik_custom/models/purchase_order_line.py | 49 ------------------------- 1 file changed, 49 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py index 766e304b..4802ebea 100755 --- a/indoteknik_custom/models/purchase_order_line.py +++ b/indoteknik_custom/models/purchase_order_line.py @@ -388,54 +388,5 @@ class PurchaseOrderLine(models.Model): else: line.delivery_amt_line = 0 - # @api.model - # def create(self, vals): - # """ - # Override method create untuk mencegah penambahan line baru - # jika status PO sudah bukan draft - # """ - # # Cek apakah ada order_id di vals - # if vals.get('order_id'): - # order = self.env['purchase.order'].browse(vals['order_id']) - # # Jika status PO bukan draft, tolak pembuatan line baru - # if order.state in ['purchase', 'done', 'cancel']: - # raise UserError(_("Tidak bisa menambahkan item baru karena PO sudah dikonfirmasi atau selesai.")) - - # return super(PurchaseOrderLine, self).create(vals) - - # def write(self, vals): - # """ - # Override method write untuk membatasi field yang bisa diubah - # jika status PO sudah bukan draft - # """ - # for line in self: - # if line.order_id.state in ['purchase', 'done', 'cancel']: - # # Hanya izinkan mengubah mark_po_line - # if 'mark_po_line' in vals and len(vals) == 1: - # # Izinkan perubahan jika hanya field mark_po_line yang diubah - # return super(PurchaseOrderLine, self).write(vals) - # else: - # _logger.info("Fields being updated: %s", vals.keys()) - # raise UserError(_("Tidak bisa mengubah item karena PO sudah dikonfirmasi atau selesai.")) - - # return super(PurchaseOrderLine, self).write(vals) - # def toggle_mark_po_line(self): - # """ - # Method khusus untuk toggle mark_po_line, aman digunakan dalam semua status PO - # """ - # for line in self: - # line.mark_po_line = not line.mark_po_line - # return True - - # def unlink(self): - # """ - # Override method unlink untuk mencegah penghapusan line - # jika status PO sudah bukan draft - # """ - # for line in self: - # if line.order_id.state in ['purchase', 'done', 'cancel']: - # raise UserError(_("Tidak bisa menghapus item karena PO sudah dikonfirmasi atau selesai.")) - - # return super(PurchaseOrderLine, self).unlink() -- cgit v1.2.3 From e5504fd30bcb1490a9de2b4af0e03fb7e22daef1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 15 May 2025 13:32:27 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1c4a6cbc..9b8f6775 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1012,7 +1012,7 @@ class StockPicking(models.Model): self.sale_id.date_doc_kirim = self.date_doc_kirim def action_assign(self): - if self.sale_id.hold_outgoing == True and self.location_id == 57 and self.location_dest_id == 60: + if self.sale_id.hold_outgoing == True and self.location_id.id == 57 and self.location_dest_id.id == 60: raise UserError("SO on hold") res = super(StockPicking, self).action_assign() current_time = datetime.datetime.utcnow() -- cgit v1.2.3 From 07efd05ec1574396ffee0c347e3f92499d174b3e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 15 May 2025 13:40:51 +0700 Subject: push disable reserved --- indoteknik_custom/models/sale_order.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 1242108e..b5f69fa2 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -372,7 +372,11 @@ class SaleOrder(models.Model): if self.hold_outgoing == True: self.hold_outgoing = False else: - pick = self.env['stock.picking'].search([('sale_id', '=', self.id), ('state', 'not in', ['cancel','done']),('BU/PICK/', 'in', self.name)]) + pick = self.env['stock.picking'].search([ + ('sale_id', '=', self.id), + ('state', 'not in', ['cancel', 'done']), + ('name', 'ilike', 'BU/PICK/%') + ]) for picking in pick: picking.do_unreserve() self.hold_outgoing = True -- cgit v1.2.3 From d75cb8d198eeca1296aa467b0d8e3fd9db9c571f Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 15 May 2025 15:09:09 +0700 Subject: Add Sales Discount in XML Faktur Pajak --- indoteknik_custom/models/coretax_fatur.py | 82 +++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 25 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index b4bffbd2..ffc2813f 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -4,19 +4,20 @@ from xml.dom import minidom import base64 import re + class CoretaxFaktur(models.Model): _name = 'coretax.faktur' _description = 'Export Faktur ke XML' - - export_file = fields.Binary(string="Export File", ) - export_filename = fields.Char(string="Export File", ) - - def validate_and_format_number(slef, input_number): + + export_file = fields.Binary(string="Export File", ) + export_filename = fields.Char(string="Export File", ) + + def validate_and_format_number(self, input_number): # Hapus semua karakter non-digit cleaned_number = re.sub(r'\D', '', input_number) - + total_sum = sum(int(char) for char in cleaned_number) - if total_sum == 0 : + if total_sum == 0: return '0000000000000000' # Hitung jumlah digit @@ -39,22 +40,16 @@ class CoretaxFaktur(models.Model): # Tambahkan elemen ListOfTaxInvoice list_of_tax_invoice = ET.SubElement(root, 'ListOfTaxInvoice') - # Dapatkan data faktur - # inv_obj = self.env['account.move'] - # invoices = inv_obj.search([('is_efaktur_exported','=',True), - # ('state','=','posted'), - # ('efaktur_id','!=', False), - # ('move_type','=','out_invoice')], limit = 5) - for invoice in invoices: tax_invoice = ET.SubElement(list_of_tax_invoice, 'TaxInvoice') buyerTIN = self.validate_and_format_number(invoice.partner_id.npwp) nitku = invoice.partner_id.nitku formula = nitku if nitku else buyerTIN.ljust(len(buyerTIN) + 6, '0') - buyerIDTKU = formula if sum(int(char) for char in buyerTIN) > 0 else '000000' + buyerIDTKU = formula if sum(int(char) for char in buyerTIN) > 0 else '000000' # Tambahkan elemen faktur - ET.SubElement(tax_invoice, 'TaxInvoiceDate').text = invoice.invoice_date.strftime('%Y-%m-%d') if invoice.invoice_date else '' + ET.SubElement(tax_invoice, 'TaxInvoiceDate').text = invoice.invoice_date.strftime( + '%Y-%m-%d') if invoice.invoice_date else '' ET.SubElement(tax_invoice, 'TaxInvoiceOpt').text = 'Normal' ET.SubElement(tax_invoice, 'TrxCode').text = '04' ET.SubElement(tax_invoice, 'AddInfo') @@ -64,30 +59,67 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'FacilityStamp') ET.SubElement(tax_invoice, 'SellerIDTKU').text = '0742260227086000000000' ET.SubElement(tax_invoice, 'BuyerTin').text = buyerTIN - ET.SubElement(tax_invoice, 'BuyerDocument').text = 'TIN' if sum(int(char) for char in buyerTIN) > 0 else 'Other ID' + ET.SubElement(tax_invoice, 'BuyerDocument').text = 'TIN' if sum( + int(char) for char in buyerTIN) > 0 else 'Other ID' ET.SubElement(tax_invoice, 'BuyerCountry').text = 'IDN' - ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = '-' if sum(int(char) for char in buyerTIN) > 0 else str(invoice.partner_id.id) + ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = '-' if sum( + int(char) for char in buyerTIN) > 0 else str(invoice.partner_id.id) ET.SubElement(tax_invoice, 'BuyerName').text = invoice.partner_id.nama_wajib_pajak or '' ET.SubElement(tax_invoice, 'BuyerAdress').text = invoice.partner_id.alamat_lengkap_text or '' ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU + # Find all product lines (exclude discounts and other adjustments) + product_lines = invoice.invoice_line_ids.filtered( + lambda l: not l.display_type and l.product_id and 'Diskon' not in l.account_id.name + ) + + # Find all discount lines + discount_lines = invoice.invoice_line_ids.filtered( + lambda l: not l.display_type and ('Diskon' in l.account_id.name or l.name and 'Diskon' in l.name) + ) + + # Calculate total product amount (before discount) + total_product_amount = sum(line.price_subtotal for line in product_lines) + if total_product_amount == 0: + total_product_amount = 1 # Avoid division by zero + + # Calculate total discount amount + total_discount_amount = abs(sum(line.price_subtotal for line in discount_lines)) + # Tambahkan elemen ListOfGoodService list_of_good_service = ET.SubElement(tax_invoice, 'ListOfGoodService') - for line in invoice.invoice_line_ids: - otherTaxBase = round(line.price_subtotal * (11/12)) if line.price_subtotal else 0 + + for line in product_lines: + # Calculate prorated discount + line_proportion = line.price_subtotal / total_product_amount + line_discount = total_discount_amount * line_proportion + + # unit_price = line.price_unit + subtotal = line.price_subtotal + quantity = line.quantity + total_discount = round(line_discount, 2) + + # Calculate tax base after discount + price_after_discount = line.price_subtotal - line_discount + + # Calculate other tax values + otherTaxBase = round(price_after_discount * (11 / 12), 2) if price_after_discount else 0 + vat_amount = round(otherTaxBase * 0.12, 2) + + # Create the line in XML good_service = ET.SubElement(list_of_good_service, 'GoodService') ET.SubElement(good_service, 'Opt').text = 'A' ET.SubElement(good_service, 'Code').text = '000000' ET.SubElement(good_service, 'Name').text = line.name ET.SubElement(good_service, 'Unit').text = 'UM.0018' - ET.SubElement(good_service, 'Price').text = str(round(line.price_subtotal/line.quantity, 2)) if line.price_subtotal else '0' - ET.SubElement(good_service, 'Qty').text = str(line.quantity) - ET.SubElement(good_service, 'TotalDiscount').text = '0' - ET.SubElement(good_service, 'TaxBase').text = str(round(line.price_subtotal)) if line.price_subtotal else '0' + ET.SubElement(good_service, 'Price').text = str(round(subtotal / quantity, 2)) if subtotal else '0' + ET.SubElement(good_service, 'Qty').text = str(quantity) + ET.SubElement(good_service, 'TotalDiscount').text = str(total_discount) + ET.SubElement(good_service, 'TaxBase').text = str(round(price_after_discount)) ET.SubElement(good_service, 'OtherTaxBase').text = str(otherTaxBase) ET.SubElement(good_service, 'VATRate').text = '12' - ET.SubElement(good_service, 'VAT').text = str(round(otherTaxBase * 0.12, 2)) + ET.SubElement(good_service, 'VAT').text = str(vat_amount) ET.SubElement(good_service, 'STLGRate').text = '0' ET.SubElement(good_service, 'STLG').text = '0' -- cgit v1.2.3 From b8acd5ee5521228eb7c9033d9a234ee2b965db1a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 15 May 2025 16:22:05 +0700 Subject: fix validation mrp bom --- indoteknik_custom/models/sale_order.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b5f69fa2..b4582f00 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1218,10 +1218,10 @@ class SaleOrder(models.Model): for order in self: for line in order.order_line: if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False: - search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)], - order='name desc') + search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id), ('sale_order', '=', order.id), ('state', '!=', 'cancel')], + order='name desc') if search_bom: - confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed') + confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed' or x.state == 'done') if not confirmed_bom: raise UserError( "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") -- cgit v1.2.3 From 3ecfd3cbf9e3257644c388801f18870960ef3ac0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 15 May 2025 21:58:34 +0700 Subject: prevent export error when state is in draft and add filter based on account_id. --- indoteknik_custom/models/coretax_fatur.py | 47 +++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index ffc2813f..c0558e5b 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -12,11 +12,20 @@ class CoretaxFaktur(models.Model): export_file = fields.Binary(string="Export File", ) export_filename = fields.Char(string="Export File", ) - def validate_and_format_number(self, input_number): + def validate_and_format_number(slef, input_number): + # mencegah error ketika mau cetak xml ketika masih di draft + if input_number is None: + return '0000000000000000' + + # ubah ke str kalau blm + if not isinstance(input_number, str): + input_number = str(input_number) + # Hapus semua karakter non-digit cleaned_number = re.sub(r'\D', '', input_number) total_sum = sum(int(char) for char in cleaned_number) + if total_sum == 0: return '0000000000000000' @@ -69,18 +78,36 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU - # Find all product lines (exclude discounts and other adjustments) + # initiate diskon id + # ACCOUNT_DISCOUNT_IDS = [463, 464, 467] + # product_lines = invoice.invoice_line_ids.filtered( + # lambda l: not l.display_type and l.product_id and + # hasattr(l, 'account_id') and l.account_id and + # l.account_id.id not in self.ACCOUNT_DISCOUNT_IDS and + # l.quantity != -1 + # ) + # discount_lines = invoice.invoice_line_ids.filtered( + # lambda l: not l.display_type and ( + # (hasattr(l, 'account_id') and l.account_id and + # l.account_id.id in self.ACCOUNT_DISCOUNT_IDS) or + # (l.quantity == -1) + # ) + # ) + # discount_id = self.env['account.account'].search([('name', '=', 'Diskon')]) + + # cari product dari inovoice line product_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and l.product_id and 'Diskon' not in l.account_id.name + lambda l: not l.display_type and l.product_id and 'Diskon' not in l.account_id.id ) - # Find all discount lines + # cari diskon/potongan discount_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and ('Diskon' in l.account_id.name or l.name and 'Diskon' in l.name) - ) + lambda l: not l.display_type and ('Diskon' in l.account_id.id or l.name and 'Diskon' in l.name) + ) # ini ke account.id # Calculate total product amount (before discount) total_product_amount = sum(line.price_subtotal for line in product_lines) + if total_product_amount == 0: total_product_amount = 1 # Avoid division by zero @@ -100,11 +127,8 @@ class CoretaxFaktur(models.Model): quantity = line.quantity total_discount = round(line_discount, 2) - # Calculate tax base after discount - price_after_discount = line.price_subtotal - line_discount - # Calculate other tax values - otherTaxBase = round(price_after_discount * (11 / 12), 2) if price_after_discount else 0 + otherTaxBase = round(subtotal * (11 / 12), 2) if subtotal else 0 vat_amount = round(otherTaxBase * 0.12, 2) # Create the line in XML @@ -116,7 +140,8 @@ class CoretaxFaktur(models.Model): ET.SubElement(good_service, 'Price').text = str(round(subtotal / quantity, 2)) if subtotal else '0' ET.SubElement(good_service, 'Qty').text = str(quantity) ET.SubElement(good_service, 'TotalDiscount').text = str(total_discount) - ET.SubElement(good_service, 'TaxBase').text = str(round(price_after_discount)) + ET.SubElement(good_service, 'TaxBase').text = str( + round(subtotal)) if subtotal else '0' ET.SubElement(good_service, 'OtherTaxBase').text = str(otherTaxBase) ET.SubElement(good_service, 'VATRate').text = '12' ET.SubElement(good_service, 'VAT').text = str(vat_amount) -- cgit v1.2.3 From a6a26665953d36578c62b6f4d5608b716d9fac88 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 15 May 2025 23:42:20 +0700 Subject: fix not iterable account_id --- indoteknik_custom/models/coretax_fatur.py | 40 ++++++++----------------------- 1 file changed, 10 insertions(+), 30 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index c0558e5b..f2a1f793 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -13,14 +13,6 @@ class CoretaxFaktur(models.Model): export_filename = fields.Char(string="Export File", ) def validate_and_format_number(slef, input_number): - # mencegah error ketika mau cetak xml ketika masih di draft - if input_number is None: - return '0000000000000000' - - # ubah ke str kalau blm - if not isinstance(input_number, str): - input_number = str(input_number) - # Hapus semua karakter non-digit cleaned_number = re.sub(r'\D', '', input_number) @@ -78,36 +70,24 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU - # initiate diskon id - # ACCOUNT_DISCOUNT_IDS = [463, 464, 467] - # product_lines = invoice.invoice_line_ids.filtered( - # lambda l: not l.display_type and l.product_id and - # hasattr(l, 'account_id') and l.account_id and - # l.account_id.id not in self.ACCOUNT_DISCOUNT_IDS and - # l.quantity != -1 - # ) - # discount_lines = invoice.invoice_line_ids.filtered( - # lambda l: not l.display_type and ( - # (hasattr(l, 'account_id') and l.account_id and - # l.account_id.id in self.ACCOUNT_DISCOUNT_IDS) or - # (l.quantity == -1) - # ) - # ) - # discount_id = self.env['account.account'].search([('name', '=', 'Diskon')]) - - # cari product dari inovoice line + # cari product product_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and l.product_id and 'Diskon' not in l.account_id.id + lambda l: not l.display_type and l.product_id and + hasattr(l, 'account_id') and l.account_id and + 'Diskon' not in (l.account_id.name or '') ) # cari diskon/potongan discount_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and ('Diskon' in l.account_id.id or l.name and 'Diskon' in l.name) - ) # ini ke account.id + lambda l: not l.display_type and ( + (hasattr(l, 'account_id') and l.account_id and 'Diskon' in (l.account_id.name or '')) or + (l.name and isinstance(l.name, str) and 'Diskon' in l.name) or + (l.quantity == -1) + ) + ) # Calculate total product amount (before discount) total_product_amount = sum(line.price_subtotal for line in product_lines) - if total_product_amount == 0: total_product_amount = 1 # Avoid division by zero -- cgit v1.2.3 From 24c52892c3143dc2474e5a2ba3c99d08ee1f7d29 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 16 May 2025 08:55:53 +0700 Subject: (andri) add field date hold/unhold SO --- indoteknik_custom/models/sale_order.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 0d4fc6c3..0ae61914 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -4,6 +4,7 @@ from odoo import fields, models, api, _ from odoo.exceptions import UserError, ValidationError from datetime import datetime, timedelta import logging, random, string, requests, math, json, re, qrcode, base64 +import pytz from io import BytesIO from collections import defaultdict @@ -302,6 +303,12 @@ class SaleOrder(models.Model): ('hold', 'Hold'), ('approve', 'Approve') ], tracking=True, string='State Cancel', copy=False) + hold_outgoing_datetime = fields.Datetime( + string='Date Hold/Unhold', + tracking=True, + readonly=True, + help='Waktu terakhir ketika status Hold Outgoing SO berubah' + ) def _compute_total_margin_excl_third_party(self): for order in self: @@ -340,11 +347,13 @@ class SaleOrder(models.Model): } } - def hold_unhold_qty_outgoing_so(self): + def hold_unhold_qty_outgoing_so(self): if self.hold_outgoing == True: self.hold_outgoing = False else: self.hold_outgoing = True + + self.hold_outgoing_datetime = fields.Datetime.now() def _validate_uniform_taxes(self): for order in self: -- cgit v1.2.3 From 4360e1fd9f3af2c18b19463773047d9939716069 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 16 May 2025 09:27:25 +0700 Subject: revisi shipment group --- indoteknik_custom/models/shipment_group.py | 6 ------ 1 file changed, 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/shipment_group.py b/indoteknik_custom/models/shipment_group.py index a4bea9c4..48a3fa21 100644 --- a/indoteknik_custom/models/shipment_group.py +++ b/indoteknik_custom/models/shipment_group.py @@ -87,9 +87,6 @@ class ShipmentGroupLine(models.Model): if self.picking_id: picking = self.env['stock.picking'].browse(self.picking_id.id) - if self.shipment_id.partner_id and self.shipment_id.partner_id != picking.partner_id: - raise UserError('Partner must be same as shipment group') - if self.shipment_id.carrier_id and self.shipment_id.carrier_id != picking.carrier_id: raise UserError('carrier must be same as shipment group') @@ -101,9 +98,6 @@ class ShipmentGroupLine(models.Model): self.carrier_id = picking.carrier_id.id self.total_colly = picking.total_mapping_koli - if not self.shipment_id.partner_id: - self.shipment_id.partner_id = picking.partner_id - if not self.shipment_id.carrier_id: self.shipment_id.carrier_id = picking.carrier_id -- cgit v1.2.3 From f8811bdf897fe0176921cc01b0ee8c9b98c883d3 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 16 May 2025 09:35:23 +0700 Subject: filter using account_id --- indoteknik_custom/models/coretax_fatur.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index f2a1f793..92ff1a72 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -12,6 +12,8 @@ class CoretaxFaktur(models.Model): export_file = fields.Binary(string="Export File", ) export_filename = fields.Char(string="Export File", ) + DISCOUNT_ACCOUNT_ID = 463 + def validate_and_format_number(slef, input_number): # Hapus semua karakter non-digit cleaned_number = re.sub(r'\D', '', input_number) @@ -70,18 +72,19 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or '' ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU - # cari product + # Filter product product_lines = invoice.invoice_line_ids.filtered( - lambda l: not l.display_type and l.product_id and - hasattr(l, 'account_id') and l.account_id and - 'Diskon' not in (l.account_id.name or '') + lambda l: not l.display_type and hasattr(l, 'account_id') and + l.account_id and l.product_id and + l.account_id.id != self.DISCOUNT_ACCOUNT_ID and + l.quantity != -1 ) - # cari diskon/potongan + # Filter discount discount_lines = invoice.invoice_line_ids.filtered( lambda l: not l.display_type and ( - (hasattr(l, 'account_id') and l.account_id and 'Diskon' in (l.account_id.name or '')) or - (l.name and isinstance(l.name, str) and 'Diskon' in l.name) or + (hasattr(l, 'account_id') and l.account_id and + l.account_id.id == self.DISCOUNT_ACCOUNT_ID) or (l.quantity == -1) ) ) -- cgit v1.2.3 From 76d3d70b10a46e4143d8a2e5e6952341d661fdf2 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 16 May 2025 11:07:18 +0700 Subject: (andri) rev field datetime hold/unhold menjadi 2 field berbeda --- indoteknik_custom/models/sale_order.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 0ae61914..17115908 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -303,11 +303,9 @@ class SaleOrder(models.Model): ('hold', 'Hold'), ('approve', 'Approve') ], tracking=True, string='State Cancel', copy=False) - hold_outgoing_datetime = fields.Datetime( - string='Date Hold/Unhold', - tracking=True, - readonly=True, - help='Waktu terakhir ketika status Hold Outgoing SO berubah' + date_hold = fields.Datetime(string='Date Hold', tracking=True, readonly=True, help='Waktu ketika SO di Hold' + ) + date_unhold = fields.Datetime(string='Date Unhold', tracking=True, readonly=True, help='Waktu ketika SO di Unhold' ) def _compute_total_margin_excl_third_party(self): @@ -350,10 +348,11 @@ class SaleOrder(models.Model): def hold_unhold_qty_outgoing_so(self): if self.hold_outgoing == True: self.hold_outgoing = False + self.date_unhold = fields.Datetime.now() else: self.hold_outgoing = True - - self.hold_outgoing_datetime = fields.Datetime.now() + self.date_hold = fields.Datetime.now() + def _validate_uniform_taxes(self): for order in self: -- cgit v1.2.3 From c61cb9fcd3d01b4ffafaa6446f9fd68b09a88ff7 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 16 May 2025 11:17:25 +0700 Subject: (andri) hapus perubahan terkait sync price to SO pada branch ini --- indoteknik_custom/models/purchase_order.py | 228 ------------------------ indoteknik_custom/models/purchase_order_line.py | 2 - 2 files changed, 230 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index d5c08660..98b367d0 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -2,7 +2,6 @@ from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from dateutil.relativedelta import relativedelta from datetime import datetime, timedelta -from odoo.tools import float_compare import logging from pytz import timezone, utc import io @@ -90,233 +89,6 @@ class PurchaseOrder(models.Model): store_name = fields.Char(string='Nama Toko') purchase_order_count = fields.Integer('Purchase Order Count', related='partner_id.purchase_order_count') - - - def sync_price_to_so(self): - updated_lines = [] - skipped_lines = [] # Untuk melacak line yang dilewati karena harga sudah sama - affected_so_ids = set() # Untuk melacak SO mana saja yang terkena dampak - - for order in self: - # Filter hanya line-line yang ditandai - marked_lines = order.order_line.filtered(lambda l: l.mark_po_line) - - if not marked_lines: - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': _('Info'), - 'message': _('Tidak ada item yang ditandai untuk disinkronkan'), - 'sticky': False, - 'type': 'info', - } - } - - # Cek apakah ada referensi ke sale order - if not order.sale_order_id and not order.order_sales_match_line: - raise UserError(_("Tidak ada Sales Order yang terkait dengan Purchase Order ini!")) - - # Jika PO dibuat dari SO langsung - if order.sale_order_id: - affected_so_ids.add(order.sale_order_id.id) - for po_line in marked_lines: - # Cari SO line yang terkait - so_line = po_line.so_line_id - if not so_line and po_line.product_id: - # Coba cari berdasarkan product jika tidak ada referensi langsung - so_line = self.env['sale.order.line'].search([ - ('product_id', '=', po_line.product_id.id), - ('order_id', '=', order.sale_order_id.id) - ], limit=1) - - if so_line: - old_price = so_line.purchase_price - - # Cek apakah harga sudah sama - if float_compare(old_price, po_line.price_unit, precision_digits=2) == 0: - skipped_lines.append({ - 'product': po_line.product_id.display_name, - 'so_name': so_line.order_id.name, - 'so_id': so_line.order_id.id, - 'price': old_price, - 'currency': order.currency_id or so_line.order_id.currency_id - }) - else: - so_line.purchase_price = po_line.price_unit - updated_lines.append({ - 'product': po_line.product_id.display_name, - 'so_line_id': so_line.id, - 'so_name': so_line.order_id.name, - 'so_id': so_line.order_id.id, - 'old_price': old_price, - 'new_price': po_line.price_unit, - 'currency': order.currency_id or so_line.order_id.currency_id - }) - - # Hapus tanda setelah sinkronisasi - po_line.mark_po_line = False - - # Jika PO terkait dengan beberapa SO (melalui order_sales_match_line) - elif order.order_sales_match_line: - for po_line in marked_lines: - # Cari match lines yang sesuai dengan product di PO line - match_lines = order.order_sales_match_line.filtered( - lambda m: m.product_id.id == po_line.product_id.id - ) - - for match_line in match_lines: - if match_line.sale_id: - affected_so_ids.add(match_line.sale_id.id) - - so_line = match_line.sale_line_id - if so_line: - old_price = so_line.purchase_price - - # Cek apakah harga sudah sama - if float_compare(old_price, po_line.price_unit, precision_digits=2) == 0: - skipped_lines.append({ - 'product': po_line.product_id.display_name, - 'so_name': so_line.order_id.name, - 'so_id': so_line.order_id.id, - 'price': old_price, - 'currency': order.currency_id or so_line.order_id.currency_id - }) - else: - so_line.purchase_price = po_line.price_unit - updated_lines.append({ - 'product': po_line.product_id.display_name, - 'so_line_id': so_line.id, - 'so_name': so_line.order_id.name, - 'so_id': so_line.order_id.id, - 'old_price': old_price, - 'new_price': po_line.price_unit, - 'currency': order.currency_id or so_line.order_id.currency_id - }) - - # Hapus tanda setelah sinkronisasi - po_line.mark_po_line = False - - # Ambil data SO yang terkena dampak - affected_sales_orders = self.env['sale.order'].browse(list(affected_so_ids)) - - # Buat pesan untuk log - message_body = "" - - # Jika ada line yang diupdate - if updated_lines: - # Kelompokkan perubahan berdasarkan SO - changes_by_so = {} - for line in updated_lines: - so_id = line['so_id'] - if so_id not in changes_by_so: - changes_by_so[so_id] = [] - changes_by_so[so_id].append(line) - - # Buat pesan untuk line yang diupdate - message_body += f"

    Harga Purchase pada Sales Order telah diperbarui dari {self.name}:

    " - - for so_id, lines in changes_by_so.items(): - so = self.env['sale.order'].browse(so_id) - # Buat link ke SO yang bisa diklik - message_body += f"

    Sales Order: {so.name}

      " - for line in lines: - # Format harga dalam format mata uang - currency = line['currency'] - old_price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['old_price'], {'display_currency': currency}) - new_price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['new_price'], {'display_currency': currency}) - - message_body += f"
    • {line['product']}:
      {old_price_formatted} → {new_price_formatted}
    • " - message_body += "
    " - - # Jika ada line yang dilewati karena harga sudah sama - if skipped_lines: - # Kelompokkan berdasarkan SO - skipped_by_so = {} - for line in skipped_lines: - so_id = line['so_id'] - if so_id not in skipped_by_so: - skipped_by_so[so_id] = [] - skipped_by_so[so_id].append(line) - - # Tambahkan pesan untuk line yang dilewati - if message_body: - message_body += "

    Item berikut dilewati karena harga sudah sama:

    " - else: - message_body += f"

    Tidak ada perubahan harga untuk PO {self.name}:

    " - message_body += "

    Item berikut sudah memiliki harga yang sama di SO:

    " - - for so_id, lines in skipped_by_so.items(): - so = self.env['sale.order'].browse(so_id) - message_body += f"

    Sales Order: {so.name}

      " - for line in lines: - # Format harga dalam format mata uang - currency = line['currency'] - price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['price'], {'display_currency': currency}) - - message_body += f"
    • {line['product']}: {price_formatted}
    • " - message_body += "
    " - - # Posting log message jika ada isi - if message_body: - subject = "Price Sync to SO" if updated_lines else "Price Sync - Harga Sama" - self.message_post(body=message_body, subject=subject) - - # Update juga log di setiap SO yang terkena dampak - if updated_lines: - for so in affected_sales_orders: - so_lines = [line for line in updated_lines if line['so_id'] == so.id] - if so_lines: - # Buat link ke PO yang bisa diklik - so_message = f"

    Harga Purchase diperbarui dari {self.name}:

      " - for line in so_lines: - # Format harga dalam format mata uang - currency = line['currency'] - old_price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['old_price'], {'display_currency': currency}) - new_price_formatted = self.env['ir.qweb.field.monetary'].value_to_html(line['new_price'], {'display_currency': currency}) - - so_message += f"
    • {line['product']}:
      {old_price_formatted} → {new_price_formatted}
    • " - so_message += "
    " - so.message_post(body=so_message, subject=f"Price Updated from PO {self.name}") - - # Recalculate margins - if updated_lines and hasattr(self, 'compute_total_margin'): - self.compute_total_margin() - - # Recalculate margins di SO juga - if updated_lines: - for so in affected_sales_orders: - if hasattr(so, 'compute_total_margin'): - so.compute_total_margin() - - # Tentukan pesan notifikasi dan tipe - if updated_lines and skipped_lines: - message = f"{len(updated_lines)} item diperbarui dan {len(skipped_lines)} item dilewati karena harga sudah sama" - title = "Sukses!" - notification_type = "success" - elif updated_lines: - message = f"{len(updated_lines)} item telah diperbarui harganya di {len(affected_so_ids)} Sales Order" - title = "Sukses!" - notification_type = "success" - elif skipped_lines: - message = f"item tersebut ({len(skipped_lines)}) sudah memiliki harga yang sama" - title = "Info" - notification_type = "info" - else: - message = "Tidak ada line yang berhasil diperbarui" - title = "Info" - notification_type = "info" - - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': _(title), - 'message': _(message), - 'sticky': False, - 'type': notification_type, - } - } # cek payment term def _check_payment_term(self): _logger.info("Check Payment Term Terpanggil") diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py index 4802ebea..315795d5 100755 --- a/indoteknik_custom/models/purchase_order_line.py +++ b/indoteknik_custom/models/purchase_order_line.py @@ -51,8 +51,6 @@ class PurchaseOrderLine(models.Model): contribution_cost_service = fields.Float(string='Contribution Cost Service', compute='_compute_doc_delivery_amt') ending_price = fields.Float(string='Ending Price', compute='_compute_doc_delivery_amt') - mark_po_line = fields.Boolean(string=' ', default=False, help='centang jika anda ingin menandai PO line ini') - def _compute_doc_delivery_amt(self): for line in self: # Inisialisasi nilai default untuk field computed -- cgit v1.2.3 From b1603152659e81ba005e580350704f54d0aaadf5 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 17 May 2025 11:05:44 +0700 Subject: add user error changing payment term when state is approved --- indoteknik_custom/models/sale_order.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b4582f00..6d33af7c 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -7,6 +7,8 @@ import logging, random, string, requests, math, json, re, qrcode, base64 from io import BytesIO from collections import defaultdict +from psutil import users + _logger = logging.getLogger(__name__) @@ -1146,6 +1148,15 @@ class SaleOrder(models.Model): return helper_ids_str.split(', ') def write(self, values): + if 'paymente_term_id' in values: + for record in self: + if record.payment_term_id not in ['cancel', + 'draft'] and record.approval_status == 'approved': + if self.env.user.has_group( + 'sales_team.group_sale_salesman') and not self.env.user.has_group( + 'sales_team.group_sale_manager'): + raise UserError('Tidak bisa mengganti payment term karena sudah approved') + helper_ids = self._get_helper_ids() if str(self.env.user.id) in helper_ids: values['helper_by_id'] = self.env.user.id @@ -1218,10 +1229,12 @@ class SaleOrder(models.Model): for order in self: for line in order.order_line: if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False: - search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id), ('sale_order', '=', order.id), ('state', '!=', 'cancel')], - order='name desc') + search_bom = self.env['mrp.production'].search( + [('product_id', '=', line.product_id.id), ('sale_order', '=', order.id), + ('state', '!=', 'cancel')], + order='name desc') if search_bom: - confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed' or x.state == 'done') + confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed' or x.state == 'done') if not confirmed_bom: raise UserError( "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") @@ -1519,7 +1532,7 @@ class SaleOrder(models.Model): if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - + if self.env.context.get('due_approve', []) == False: if order.validate_partner_invoice_due(): return self._create_notification_action('Notification', -- cgit v1.2.3 From a9b205f38697c53410a2859aff7b2d79acc9dcd0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 17 May 2025 11:06:37 +0700 Subject: fix typo --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 4202626f..71bbd648 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1156,7 +1156,7 @@ class SaleOrder(models.Model): return helper_ids_str.split(', ') def write(self, values): - if 'paymente_term_id' in values: + if 'payment_term_id' in values: for record in self: if record.payment_term_id not in ['cancel', 'draft'] and record.approval_status == 'approved': -- cgit v1.2.3 From cb1e9095e79faddd7d2f006a7c7700bff37076fa Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 17 May 2025 12:04:31 +0700 Subject: fix bug due extension --- indoteknik_custom/models/sale_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index b4582f00..66d7d148 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1271,7 +1271,7 @@ class SaleOrder(models.Model): if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - if self.env.context.get('due_approve', []) == False: + if not self.env.context.get('due_approve', []): if order.validate_partner_invoice_due(): return self._create_notification_action('Notification', 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') @@ -1520,7 +1520,7 @@ class SaleOrder(models.Model): if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - if self.env.context.get('due_approve', []) == False: + if not self.env.context.get('due_approve', []): if order.validate_partner_invoice_due(): return self._create_notification_action('Notification', 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') -- cgit v1.2.3 From 2c2a1ac7fe9530390b4bbc473d629ddfb2b97d2b Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 17 May 2025 13:26:59 +0700 Subject: remove permission for sales manager to edit --- indoteknik_custom/models/sale_order.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 71bbd648..57a5000e 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1160,10 +1160,7 @@ class SaleOrder(models.Model): for record in self: if record.payment_term_id not in ['cancel', 'draft'] and record.approval_status == 'approved': - if self.env.user.has_group( - 'sales_team.group_sale_salesman') and not self.env.user.has_group( - 'sales_team.group_sale_manager'): - raise UserError('Tidak bisa mengganti payment term karena sudah approved') + raise UserError('Tidak bisa mengganti payment term karena sudah approved') helper_ids = self._get_helper_ids() if str(self.env.user.id) in helper_ids: -- cgit v1.2.3 From 434260b95902e088820c3b5d14d7a565f01a21c1 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 17 May 2025 13:55:59 +0700 Subject: fix error --- indoteknik_custom/models/sale_order.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 57a5000e..8a57b9c0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -334,9 +334,9 @@ class SaleOrder(models.Model): ('approve', 'Approve') ], tracking=True, string='State Cancel', copy=False) date_hold = fields.Datetime(string='Date Hold', tracking=True, readonly=True, help='Waktu ketika SO di Hold' - ) + ) date_unhold = fields.Datetime(string='Date Unhold', tracking=True, readonly=True, help='Waktu ketika SO di Unhold' - ) + ) def _compute_total_margin_excl_third_party(self): for order in self: @@ -375,7 +375,7 @@ class SaleOrder(models.Model): } } - def hold_unhold_qty_outgoing_so(self): + def hold_unhold_qty_outgoing_so(self): if self.hold_outgoing == True: self.hold_outgoing = False self.date_unhold = fields.Datetime.now() @@ -389,7 +389,6 @@ class SaleOrder(models.Model): picking.do_unreserve() self.hold_outgoing = True self.date_hold = fields.Datetime.now() - def _validate_uniform_taxes(self): for order in self: @@ -1157,9 +1156,9 @@ class SaleOrder(models.Model): def write(self, values): if 'payment_term_id' in values: - for record in self: - if record.payment_term_id not in ['cancel', - 'draft'] and record.approval_status == 'approved': + if self.state not in ['cancel', 'draft'] and self.approval_status == 'approved': + if self.env.user.has_group('sales_team.group_sale_salesman') or self.env.user.has_group( + 'sales_team.group_sale_manager'): raise UserError('Tidak bisa mengganti payment term karena sudah approved') helper_ids = self._get_helper_ids() -- cgit v1.2.3 From 434bf64f3229b12ec938671d74446389cb0455aa Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sun, 18 May 2025 01:50:35 +0700 Subject: prevent sales member or sales manager to change payment term when so/approved --- indoteknik_custom/models/sale_order.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8a57b9c0..bfb38f6d 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1032,12 +1032,6 @@ class SaleOrder(models.Model): line_no += 1 line.line_no = line_no - def write(self, vals): - if 'carrier_id' in vals: - for picking in self.picking_ids: - if picking.state == 'assigned': - picking.carrier_id = self.carrier_id - def calculate_so_status(self): so_state = ['sale'] sales = self.search([ @@ -1154,8 +1148,13 @@ class SaleOrder(models.Model): helper_ids_str = self.env['ir.config_parameter'].sudo().get_param('sale.order.user_helper_ids') return helper_ids_str.split(', ') - def write(self, values): - if 'payment_term_id' in values: + def write(self, vals): + if 'carrier_id' in vals: + for picking in self.picking_ids: + if picking.state == 'assigned': + picking.carrier_id = self.carrier_id + + if 'payment_term_id' in vals: if self.state not in ['cancel', 'draft'] and self.approval_status == 'approved': if self.env.user.has_group('sales_team.group_sale_salesman') or self.env.user.has_group( 'sales_team.group_sale_manager'): @@ -1163,9 +1162,9 @@ class SaleOrder(models.Model): helper_ids = self._get_helper_ids() if str(self.env.user.id) in helper_ids: - values['helper_by_id'] = self.env.user.id + vals['helper_by_id'] = self.env.user.id - return super(SaleOrder, self).write(values) + return super(SaleOrder, self).write(vals) def check_due(self): """To show the due amount and warning stage""" -- cgit v1.2.3 From 26b47c8647d3c66e6f659efc31be3deccfe026c9 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 19 May 2025 08:37:31 +0700 Subject: (andri) fix log note double pada product variants --- indoteknik_custom/models/product_template.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 2679fbfd..5cb3da88 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -870,9 +870,9 @@ class ProductTemplate(models.Model): record.message_post(body=f"Updated:
      {''.join(changes)}
    ") # log changes to product variants - variant_message = f"Updated:
      {''.join(changes)}
    " - for variant in record.product_variant_ids: - variant.message_post(body=variant_message) + # variant_message = f"Updated:
      {''.join(changes)}
    " + # for variant in record.product_variant_ids: + # variant.message_post(body=variant_message) # simpan data lama dan log perubahan field def write(self, vals): -- cgit v1.2.3 From abb5b01c3bbaf19feaf1be8f3a3fac6b95a1d6a6 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Mon, 19 May 2025 15:06:58 +0700 Subject: bf qty delivered minus --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 9b8f6775..128efee6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1012,12 +1012,12 @@ class StockPicking(models.Model): self.sale_id.date_doc_kirim = self.date_doc_kirim def action_assign(self): - if self.sale_id.hold_outgoing == True and self.location_id.id == 57 and self.location_dest_id.id == 60: + if self.sale_id.hold_outgoing and self.location_id.id == 57 and self.location_dest_id.id == 60: raise UserError("SO on hold") res = super(StockPicking, self).action_assign() - current_time = datetime.datetime.utcnow() - self.real_shipping_id = self.sale_id.real_shipping_id - self.date_availability = current_time + # current_time = datetime.datetime.utcnow() + # self.real_shipping_id = self.sale_id.real_shipping_id + # self.date_availability = current_time # self.check_state_reserve() return res -- cgit v1.2.3 From 62088f63cc7904c07fa3e95d7dd487fb9cd4926c Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Mon, 19 May 2025 15:54:45 +0700 Subject: bf minus delivered qty --- indoteknik_custom/models/sale_order_line.py | 13 +++++++++++++ indoteknik_custom/models/stock_picking.py | 13 +++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 2450abd4..049bee2d 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -41,6 +41,19 @@ class SaleOrderLine(models.Model): qty_free_bu = fields.Float(string='Free BU', compute='_get_qty_free_bandengan') desc_updatable = fields.Boolean(string='desc boolean', default=True, compute='_get_desc_updatable') + def _get_outgoing_incoming_moves(self): + outgoing_moves = self.env['stock.move'] + incoming_moves = self.env['stock.move'] + + for move in self.move_ids.filtered(lambda r: r.state != 'cancel' and not r.scrapped and self.product_id == r.product_id): + if move.location_dest_id.usage == "customer": + if not move.origin_returned_move_id or (move.origin_returned_move_id and move.to_refund): + outgoing_moves |= move + elif move.location_id.usage == "customer" and move.to_refund: + incoming_moves |= move + + return outgoing_moves, incoming_moves + def _get_desc_updatable(self): for line in self: if line.product_id.id != 417724 and line.product_id.id: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 128efee6..0071bb1a 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1013,13 +1013,14 @@ class StockPicking(models.Model): def action_assign(self): if self.sale_id.hold_outgoing and self.location_id.id == 57 and self.location_dest_id.id == 60: - raise UserError("SO on hold") - res = super(StockPicking, self).action_assign() - # current_time = datetime.datetime.utcnow() - # self.real_shipping_id = self.sale_id.real_shipping_id - # self.date_availability = current_time + print(1) + else: + res = super(StockPicking, self).action_assign() + current_time = datetime.datetime.utcnow() + self.real_shipping_id = self.sale_id.real_shipping_id + self.date_availability = current_time # self.check_state_reserve() - return res + return res def ask_approval(self): if self.env.user.is_accounting: -- cgit v1.2.3 From be8056d1b69d4795724f164958171018d9968f46 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 20 May 2025 05:23:26 +0700 Subject: set payment term readonly when on any approval process --- indoteknik_custom/models/sale_order.py | 40 +++++++++++++++------------------- 1 file changed, 18 insertions(+), 22 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bbd2cd71..c7e713d0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -8,8 +8,6 @@ import pytz from io import BytesIO from collections import defaultdict -from psutil import users - _logger = logging.getLogger(__name__) @@ -334,9 +332,9 @@ class SaleOrder(models.Model): ('approve', 'Approve') ], tracking=True, string='State Cancel', copy=False) date_hold = fields.Datetime(string='Date Hold', tracking=True, readonly=True, help='Waktu ketika SO di Hold' - ) + ) date_unhold = fields.Datetime(string='Date Unhold', tracking=True, readonly=True, help='Waktu ketika SO di Unhold' - ) + ) def _compute_total_margin_excl_third_party(self): for order in self: @@ -389,7 +387,6 @@ class SaleOrder(models.Model): picking.do_unreserve() self.hold_outgoing = True self.date_hold = fields.Datetime.now() - def _validate_uniform_taxes(self): for order in self: @@ -407,8 +404,8 @@ class SaleOrder(models.Model): # if rec.fee_third_party == 0 and rec.total_margin_excl_third_party != rec.total_percent_margin: # # Gunakan direct SQL atau flag context untuk menghindari rekursi # self.env.cr.execute(""" - # UPDATE sale_order - # SET total_margin_excl_third_party = %s + # UPDATE sale_order + # SET total_margin_excl_third_party = %s # WHERE id = %s # """, (rec.total_percent_margin, rec.id)) # self.invalidate_cache() @@ -1155,23 +1152,12 @@ class SaleOrder(models.Model): helper_ids_str = self.env['ir.config_parameter'].sudo().get_param('sale.order.user_helper_ids') return helper_ids_str.split(', ') - def write(self, vals): - if 'carrier_id' in vals: - for picking in self.picking_ids: - if picking.state == 'assigned': - picking.carrier_id = self.carrier_id - - if 'payment_term_id' in vals: - if self.state not in ['cancel', 'draft'] and self.approval_status == 'approved': - if self.env.user.has_group('sales_team.group_sale_salesman') or self.env.user.has_group( - 'sales_team.group_sale_manager'): - raise UserError('Tidak bisa mengganti payment term karena sudah approved') - + def write(self, values): helper_ids = self._get_helper_ids() if str(self.env.user.id) in helper_ids: - vals['helper_by_id'] = self.env.user.id + values['helper_by_id'] = self.env.user.id - return super(SaleOrder, self).write(vals) + return super(SaleOrder, self).write(values) def check_due(self): """To show the due amount and warning stage""" @@ -1542,7 +1528,7 @@ class SaleOrder(models.Model): if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - + if not self.env.context.get('due_approve', []): if order.validate_partner_invoice_due(): return self._create_notification_action('Notification', @@ -2023,3 +2009,13 @@ class SaleOrder(models.Model): if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() return res + + def write(self, vals): + # Cek apakah payment_term_id diubah dan SO sedang dalam approval + if 'payment_term_id' in vals and any( + order.approval_status in ['pengajuan1', 'pengajuan2', 'approved'] for order in self): + raise UserError( + "Payment Term tidak dapat diubah karena Sales Order sedang dalam proses approval atau sudah diapprove.") + + # Lanjutkan dengan logika write yang sudah ada + return super(SaleOrder, self).write(vals) -- cgit v1.2.3 From accaff72daae402924f89c2347b57b75901f542b Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 20 May 2025 10:09:16 +0700 Subject: if payment term != customer payment term cannot save --- indoteknik_custom/models/sale_order.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c7e713d0..e977e07c 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2017,5 +2017,17 @@ class SaleOrder(models.Model): raise UserError( "Payment Term tidak dapat diubah karena Sales Order sedang dalam proses approval atau sudah diapprove.") - # Lanjutkan dengan logika write yang sudah ada + if 'payment_term_id' in vals: + for order in self: + partner = order.partner_id.parent_id or order.partner_id + customer_payment_term = partner.property_payment_term_id + + # Jika payment term yang diinput berbeda dengan payment term customer + if vals['payment_term_id'] != customer_payment_term.id: + raise UserError( + f"Payment Term berbeda pada Master Data Customer. " + f"Harap ganti ke '{customer_payment_term.name}' " + f"sesuai dengan payment term yang terdaftar pada customer." + ) + return super(SaleOrder, self).write(vals) -- cgit v1.2.3 From c8d4530d4ade041378691613acd642963bd84c31 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 21 May 2025 15:23:40 +0700 Subject: trying to fix bug matches so po --- indoteknik_custom/models/automatic_purchase.py | 45 +++++++++++++++++++------- 1 file changed, 33 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index b66121e1..ff10b814 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -696,21 +696,42 @@ class SaleNotInMatchPO(models.Model): purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax') note_procurement = fields.Many2one(string='Note Procurement') + # 1. yang bug + # def init(self): + # tools.drop_view_if_exists(self.env.cr, self._table) + # self.env.cr.execute(""" + # CREATE OR REPLACE VIEW %s AS( + # select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id, + # apsm.picking_id, apsm.move_id, apsm.partner_id, + # apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid, + # apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price, + # apsm.purchase_tax_id, apsm.note_procurement + # from automatic_purchase_sales_match apsm + # where apsm.sale_line_id not in ( + # select distinct coalesce(posm.sale_line_id,0) + # from purchase_order_sales_match posm + # join purchase_order po on po.id = posm.purchase_order_id + # where po.state not in ('cancel') + # ) + # ) + # """ % self._table) + def init(self): tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute(""" CREATE OR REPLACE VIEW %s AS( - select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id, - apsm.picking_id, apsm.move_id, apsm.partner_id, - apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid, - apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price, - apsm.purchase_tax_id, apsm.note_procurement - from automatic_purchase_sales_match apsm - where apsm.sale_line_id not in ( - select distinct coalesce(posm.sale_line_id,0) - from purchase_order_sales_match posm - join purchase_order po on po.id = posm.purchase_order_id - where po.state not in ('cancel') + SELECT apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, + apsm.sale_id, apsm.sale_line_id, apsm.picking_id, apsm.move_id, + apsm.partner_id, apsm.partner_invoice_id, apsm.salesperson_id, + apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid, + apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price, + apsm.purchase_tax_id, apsm.note_procurement + FROM automatic_purchase_sales_match apsm + WHERE apsm.sale_line_id NOT IN ( + SELECT posm.sale_line_id + FROM purchase_order_sales_match posm + JOIN purchase_order po ON po.id = posm.purchase_order_id + WHERE po.state NOT IN ('cancel') ) ) - """ % self._table) + """ % self._table) \ No newline at end of file -- cgit v1.2.3 From 1047274c07dc302e9c6b3079e6ba7cc9349a9bd0 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 22 May 2025 11:50:12 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0071bb1a..87255ab6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -584,6 +584,7 @@ class StockPicking(models.Model): self.lalamove_phone = phone self.lalamove_status = pod.get("status") self.lalamove_delivered_at = delivered_at_dt + self.driver_arrival_date = delivered_at_dt return data raise UserError("No delivered data found in Lalamove response.") -- cgit v1.2.3 From cbf9eff4c2f934491b3a1adbf56255c14c4852ea Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 22 May 2025 14:00:28 +0700 Subject: push --- indoteknik_custom/models/sale_advance_payment_inv.py | 1 + indoteknik_custom/models/stock_picking.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_advance_payment_inv.py b/indoteknik_custom/models/sale_advance_payment_inv.py index bea9a900..48b753cc 100644 --- a/indoteknik_custom/models/sale_advance_payment_inv.py +++ b/indoteknik_custom/models/sale_advance_payment_inv.py @@ -197,6 +197,7 @@ class SaleAdvancePaymentInv(models.TransientModel): # if final: # moves.sudo().filtered(lambda m: m.amount_total < 0).action_switch_invoice_into_refund_credit_note() # for move in moves: + # move.message_post_with_view('mail.message_origin_link', # values={'self': move, 'origin': move.line_ids.mapped('sale_line_ids.order_id')}, # subtype_id=self.env.ref('mail.mt_note').id diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 87255ab6..3135f41c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -288,7 +288,7 @@ class StockPicking(models.Model): self.ensure_one() if not self.name or not self.origin: return False - return f"{self.name} {self.origin}" + return f"{self.name}, {self.origin}" def _download_pod_photo(self, url): """Mengunduh foto POD dari URL""" -- cgit v1.2.3 From 558130bbf48c33ddfa6080450c80bc8801a570f0 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 22 May 2025 15:34:03 +0700 Subject: form-merchant & quotation tampilan --- indoteknik_custom/models/sale_order.py | 43 +++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 0d4fc6c3..6ccb6fde 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -303,6 +303,12 @@ class SaleOrder(models.Model): ('approve', 'Approve') ], tracking=True, string='State Cancel', copy=False) + ready_to_ship_status_detail = fields.Char( + string='Status Shipping Detail', + compute='_compute_ready_to_ship_status_detail' + ) + + def _compute_total_margin_excl_third_party(self): for order in self: if order.amount_untaxed == 0: @@ -1939,4 +1945,39 @@ class SaleOrder(models.Model): self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() - return res \ No newline at end of file + return res + + # @api.depends('commitment_date') + def _compute_ready_to_ship_status_detail(self): + for order in self: + eta = order.commitment_date + + match_lines = self.env['purchase.order.sales.match'].search([ + ('sale_id', '=', order.id) + ]) + + if match_lines: + for match in match_lines: + po = match.purchase_order_id + product = match.product_id + + po_line = self.env['purchase.order.line'].search([ + ('order_id', '=', po.id), + ('product_id', '=', product.id) + ], limit=1) + + stock_move = self.env['stock.move'].search([ + ('purchase_line_id', '=', po_line.id) + ], limit=1) + picking_in = stock_move.picking_id + + result_date = picking_in.date_done if picking_in else None + if result_date: + status = "Early" if result_date < eta else "Delay" + result_date_str = result_date.strftime('%m/%d/%Y') + eta_str = eta.strftime('%m/%d/%Y') + order.ready_to_ship_status_detail = f"Expected: {eta_str} | Realtime: {result_date_str} | {status}" + else: + order.ready_to_ship_status_detail = "On Track" + else: + order.ready_to_ship_status_detail = 'On Track' \ No newline at end of file -- cgit v1.2.3 From cc6e4b9e48752ee54b42db2c16fae7cd5d5af1c4 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 09:02:14 +0700 Subject: quick fix purchasing job conflict with manufacturing order sync to po --- indoteknik_custom/models/automatic_purchase.py | 4 ++-- indoteknik_custom/models/product_template.py | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index ff10b814..518dc74c 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -728,10 +728,10 @@ class SaleNotInMatchPO(models.Model): apsm.purchase_tax_id, apsm.note_procurement FROM automatic_purchase_sales_match apsm WHERE apsm.sale_line_id NOT IN ( - SELECT posm.sale_line_id + SELECT distinct coalesce(posm.sale_line_id,0) FROM purchase_order_sales_match posm JOIN purchase_order po ON po.id = posm.purchase_order_id WHERE po.state NOT IN ('cancel') ) ) - """ % self._table) \ No newline at end of file + """ % self._table) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 89392033..6903b53e 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -913,7 +913,8 @@ class ProductProduct(models.Model): qty_onhand_bandengan = fields.Float(string='Onhand BU', compute='_get_qty_onhand_bandengan') clean_website_description = fields.Char(string='Clean Website Description', compute='_get_clean_website_description') qty_incoming_bandengan = fields.Float(string='Incoming BU', compute='_get_qty_incoming_bandengan') - qty_outgoing_bandengan = fields.Float(string='Outgoing BU', compute='_get_qty_outgoing_bandengan') + qty_outgoing_bandengan = fields.Float(string='Outgoing BU', compute='_get_qty_outgoing_bandengan', help='only outgoing from sales order bandengan') + qty_outgoing_mo_bandengan = fields.Float(string='Outgoing MO BU', compute='_get_qty_outgoing_mo_bandengan', help='only outgoing from manufacturing order bandengan') qty_available_bandengan = fields.Float(string='Available BU', compute='_get_qty_available_bandengan') qty_free_bandengan = fields.Float(string='Free BU', compute='_get_qty_free_bandengan') qty_upcoming = fields.Float(string='Qty Upcoming', compute='_get_qty_upcoming') @@ -1115,12 +1116,23 @@ class ProductProduct(models.Model): domain=[ ('product_id', '=', product.id), ('location_id', 'in', [57, 83]), + ('mo_id', '=', False) ], fields=['qty_need'], groupby=[] )[0].get('qty_need', 0.0) product.qty_outgoing_bandengan = qty + def _get_qty_outgoing_mo_bandengan(self): + for product in self: + records = self.env['v.move.outstanding'].search([ + ('product_id.id', '=', product.id), + ('location_id.id', 'in', [57, 83]), + ('mo_id.id', '>', 0) + ]) + qty = sum(records.mapped('qty_need') or [0.0]) + product.qty_outgoing_mo_bandengan = qty + def _get_qty_onhand_bandengan(self): for product in self: qty_onhand = self.env['stock.quant'].search([ @@ -1132,7 +1144,7 @@ class ProductProduct(models.Model): def _get_qty_available_bandengan(self): for product in self: - qty_available = product.qty_incoming_bandengan + product.qty_onhand_bandengan - product.qty_outgoing_bandengan + qty_available = product.qty_incoming_bandengan + product.qty_onhand_bandengan - product.qty_outgoing_bandengan - product.qty_outgoing_mo_bandengan product.qty_available_bandengan = qty_available or 0 def _get_qty_free_bandengan(self): @@ -1334,6 +1346,7 @@ class ProductProduct(models.Model): else: return super().write(vals) + class OutstandingMove(models.Model): _name = 'v.move.outstanding' _auto = False @@ -1345,6 +1358,7 @@ class OutstandingMove(models.Model): qty_need = fields.Float(string='Qty Need', help='Qty yang akan outgoing / incoming') location_id = fields.Many2one('stock.location', string='Location', help='Lokasi asal') location_dest_id = fields.Many2one('stock.location', string='Location To', help='Lokasi tujuan') + mo_id = fields.Many2one('mrp.production', string='Manufacturing Order') def init(self): # where clause 'state in' follow the origin of outgoing and incoming odoo @@ -1353,7 +1367,8 @@ class OutstandingMove(models.Model): CREATE OR REPLACE VIEW %s AS select sm.id, sm.reference, sm.product_id, sm.product_uom_qty as qty_need, - sm.location_id, sm.location_dest_id + sm.location_id, sm.location_dest_id, + sm.raw_material_production_id as mo_id from stock_move sm where 1=1 and sm.state in( -- cgit v1.2.3 From 46e4c80e1b530274c01cca5603f3e1be873820f6 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 09:21:45 +0700 Subject: bf cant create invoice cause of custom method action assign wrong implement --- indoteknik_custom/models/stock_picking.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3135f41c..504338b0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1013,15 +1013,15 @@ class StockPicking(models.Model): self.sale_id.date_doc_kirim = self.date_doc_kirim def action_assign(self): - if self.sale_id.hold_outgoing and self.location_id.id == 57 and self.location_dest_id.id == 60: - print(1) - else: - res = super(StockPicking, self).action_assign() + res = super(StockPicking, self).action_assign() + for move in self: + # if not move.sale_id.hold_outgoing and move.location_id.id != 57 and move.location_dest_id.id != 60: + # TODO cant skip hold outgoing cause of not singleton method current_time = datetime.datetime.utcnow() - self.real_shipping_id = self.sale_id.real_shipping_id - self.date_availability = current_time + move.real_shipping_id = move.sale_id.real_shipping_id + move.date_availability = current_time # self.check_state_reserve() - return res + return res def ask_approval(self): if self.env.user.is_accounting: -- cgit v1.2.3 From 4fc520a9b0d1e2e809033b35562fd80fffad472d Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 09:37:45 +0700 Subject: Revert "bf cant create invoice cause of custom method action assign wrong implement" This reverts commit 46e4c80e1b530274c01cca5603f3e1be873820f6. --- indoteknik_custom/models/stock_picking.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 504338b0..3135f41c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1013,15 +1013,15 @@ class StockPicking(models.Model): self.sale_id.date_doc_kirim = self.date_doc_kirim def action_assign(self): - res = super(StockPicking, self).action_assign() - for move in self: - # if not move.sale_id.hold_outgoing and move.location_id.id != 57 and move.location_dest_id.id != 60: - # TODO cant skip hold outgoing cause of not singleton method + if self.sale_id.hold_outgoing and self.location_id.id == 57 and self.location_dest_id.id == 60: + print(1) + else: + res = super(StockPicking, self).action_assign() current_time = datetime.datetime.utcnow() - move.real_shipping_id = move.sale_id.real_shipping_id - move.date_availability = current_time + self.real_shipping_id = self.sale_id.real_shipping_id + self.date_availability = current_time # self.check_state_reserve() - return res + return res def ask_approval(self): if self.env.user.is_accounting: -- cgit v1.2.3 From dbf09bbf792809e84d2d0330a992e42b3a5c6994 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 09:38:02 +0700 Subject: Revert "Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into odoo-backup" This reverts commit 43313db30da73b87843425c01c723f66ee982886, reversing changes made to f6f59e660af6c4229ada54f7313d68867df1ba15. --- indoteknik_custom/models/automatic_purchase.py | 45 ++++++--------------- indoteknik_custom/models/product_template.py | 6 +-- .../models/sale_advance_payment_inv.py | 1 - indoteknik_custom/models/sale_order.py | 46 +++++----------------- indoteknik_custom/models/stock_picking.py | 3 +- 5 files changed, 26 insertions(+), 75 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index ff10b814..b66121e1 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -696,42 +696,21 @@ class SaleNotInMatchPO(models.Model): purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax') note_procurement = fields.Many2one(string='Note Procurement') - # 1. yang bug - # def init(self): - # tools.drop_view_if_exists(self.env.cr, self._table) - # self.env.cr.execute(""" - # CREATE OR REPLACE VIEW %s AS( - # select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id, - # apsm.picking_id, apsm.move_id, apsm.partner_id, - # apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid, - # apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price, - # apsm.purchase_tax_id, apsm.note_procurement - # from automatic_purchase_sales_match apsm - # where apsm.sale_line_id not in ( - # select distinct coalesce(posm.sale_line_id,0) - # from purchase_order_sales_match posm - # join purchase_order po on po.id = posm.purchase_order_id - # where po.state not in ('cancel') - # ) - # ) - # """ % self._table) - def init(self): tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute(""" CREATE OR REPLACE VIEW %s AS( - SELECT apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, - apsm.sale_id, apsm.sale_line_id, apsm.picking_id, apsm.move_id, - apsm.partner_id, apsm.partner_invoice_id, apsm.salesperson_id, - apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid, - apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price, - apsm.purchase_tax_id, apsm.note_procurement - FROM automatic_purchase_sales_match apsm - WHERE apsm.sale_line_id NOT IN ( - SELECT posm.sale_line_id - FROM purchase_order_sales_match posm - JOIN purchase_order po ON po.id = posm.purchase_order_id - WHERE po.state NOT IN ('cancel') + select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id, + apsm.picking_id, apsm.move_id, apsm.partner_id, + apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid, + apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price, + apsm.purchase_tax_id, apsm.note_procurement + from automatic_purchase_sales_match apsm + where apsm.sale_line_id not in ( + select distinct coalesce(posm.sale_line_id,0) + from purchase_order_sales_match posm + join purchase_order po on po.id = posm.purchase_order_id + where po.state not in ('cancel') ) ) - """ % self._table) \ No newline at end of file + """ % self._table) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 89392033..17805c6c 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -870,9 +870,9 @@ class ProductTemplate(models.Model): record.message_post(body=f"Updated:
      {''.join(changes)}
    ") # log changes to product variants - # variant_message = f"Updated:
      {''.join(changes)}
    " - # for variant in record.product_variant_ids: - # variant.message_post(body=variant_message) + variant_message = f"Updated:
      {''.join(changes)}
    " + for variant in record.product_variant_ids: + variant.message_post(body=variant_message) # simpan data lama dan log perubahan field def write(self, vals): diff --git a/indoteknik_custom/models/sale_advance_payment_inv.py b/indoteknik_custom/models/sale_advance_payment_inv.py index 48b753cc..bea9a900 100644 --- a/indoteknik_custom/models/sale_advance_payment_inv.py +++ b/indoteknik_custom/models/sale_advance_payment_inv.py @@ -197,7 +197,6 @@ class SaleAdvancePaymentInv(models.TransientModel): # if final: # moves.sudo().filtered(lambda m: m.amount_total < 0).action_switch_invoice_into_refund_credit_note() # for move in moves: - # move.message_post_with_view('mail.message_origin_link', # values={'self': move, 'origin': move.line_ids.mapped('sale_line_ids.order_id')}, # subtype_id=self.env.ref('mail.mt_note').id diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index db0e1227..6028a1d0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -332,9 +332,9 @@ class SaleOrder(models.Model): ('approve', 'Approve') ], tracking=True, string='State Cancel', copy=False) date_hold = fields.Datetime(string='Date Hold', tracking=True, readonly=True, help='Waktu ketika SO di Hold' - ) + ) date_unhold = fields.Datetime(string='Date Unhold', tracking=True, readonly=True, help='Waktu ketika SO di Unhold' - ) + ) ready_to_ship_status_detail = fields.Char( string='Status Shipping Detail', @@ -379,7 +379,7 @@ class SaleOrder(models.Model): } } - def hold_unhold_qty_outgoing_so(self): + def hold_unhold_qty_outgoing_so(self): if self.hold_outgoing == True: self.hold_outgoing = False self.date_unhold = fields.Datetime.now() @@ -393,6 +393,7 @@ class SaleOrder(models.Model): picking.do_unreserve() self.hold_outgoing = True self.date_hold = fields.Datetime.now() + def _validate_uniform_taxes(self): for order in self: @@ -410,8 +411,8 @@ class SaleOrder(models.Model): # if rec.fee_third_party == 0 and rec.total_margin_excl_third_party != rec.total_percent_margin: # # Gunakan direct SQL atau flag context untuk menghindari rekursi # self.env.cr.execute(""" - # UPDATE sale_order - # SET total_margin_excl_third_party = %s + # UPDATE sale_order + # SET total_margin_excl_third_party = %s # WHERE id = %s # """, (rec.total_percent_margin, rec.id)) # self.invalidate_cache() @@ -1231,12 +1232,10 @@ class SaleOrder(models.Model): for order in self: for line in order.order_line: if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False: - search_bom = self.env['mrp.production'].search( - [('product_id', '=', line.product_id.id), ('sale_order', '=', order.id), - ('state', '!=', 'cancel')], - order='name desc') + search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id), ('sale_order', '=', order.id), ('state', '!=', 'cancel')], + order='name desc') if search_bom: - confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed' or x.state == 'done') + confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed' or x.state == 'done') if not confirmed_bom: raise UserError( "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.") @@ -1534,7 +1533,7 @@ class SaleOrder(models.Model): if not order.real_shipping_id: UserError('Real Delivery Address harus di isi') - + if not self.env.context.get('due_approve', []): if order.validate_partner_invoice_due(): return self._create_notification_action('Notification', @@ -2016,7 +2015,6 @@ class SaleOrder(models.Model): <<<<<<< HEAD self._calculate_etrts_date() return res -<<<<<<< HEAD ======= self._calculate_etrts_date() return res @@ -2056,27 +2054,3 @@ class SaleOrder(models.Model): else: order.ready_to_ship_status_detail = 'On Track' >>>>>>> CR/form-merchant -======= - - def write(self, vals): - # Cek apakah payment_term_id diubah dan SO sedang dalam approval - if 'payment_term_id' in vals and any( - order.approval_status in ['pengajuan1', 'pengajuan2', 'approved'] for order in self): - raise UserError( - "Payment Term tidak dapat diubah karena Sales Order sedang dalam proses approval atau sudah diapprove.") - - if 'payment_term_id' in vals: - for order in self: - partner = order.partner_id.parent_id or order.partner_id - customer_payment_term = partner.property_payment_term_id - - # Jika payment term yang diinput berbeda dengan payment term customer - if vals['payment_term_id'] != customer_payment_term.id: - raise UserError( - f"Payment Term berbeda pada Master Data Customer. " - f"Harap ganti ke '{customer_payment_term.name}' " - f"sesuai dengan payment term yang terdaftar pada customer." - ) - - return super(SaleOrder, self).write(vals) ->>>>>>> 46e4c80e1b530274c01cca5603f3e1be873820f6 diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3135f41c..0071bb1a 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -288,7 +288,7 @@ class StockPicking(models.Model): self.ensure_one() if not self.name or not self.origin: return False - return f"{self.name}, {self.origin}" + return f"{self.name} {self.origin}" def _download_pod_photo(self, url): """Mengunduh foto POD dari URL""" @@ -584,7 +584,6 @@ class StockPicking(models.Model): self.lalamove_phone = phone self.lalamove_status = pod.get("status") self.lalamove_delivered_at = delivered_at_dt - self.driver_arrival_date = delivered_at_dt return data raise UserError("No delivered data found in Lalamove response.") -- cgit v1.2.3 From f66710c029d678ecf580808b5aee3821c90d9598 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 09:38:49 +0700 Subject: Revert "Merge branch 'CR/form-merchant' into odoo-backup" This reverts commit f6f59e660af6c4229ada54f7313d68867df1ba15, reversing changes made to 68378dd2fb8d61b282a672ca0f09033d15d82283. --- indoteknik_custom/models/__init__.py | 2 - indoteknik_custom/models/res_partner.py | 184 +--------------------- indoteknik_custom/models/sale_order.py | 46 ------ indoteknik_custom/models/user_form_merchant.py | 98 ------------ indoteknik_custom/models/user_merchant_request.py | 125 --------------- 5 files changed, 1 insertion(+), 454 deletions(-) delete mode 100644 indoteknik_custom/models/user_form_merchant.py delete mode 100644 indoteknik_custom/models/user_merchant_request.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index e17f68d1..08fa9803 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -144,8 +144,6 @@ from . import stock_immediate_transfer from . import coretax_fatur from . import public_holiday from . import ir_actions_report -from . import user_form_merchant -from . import user_merchant_request from . import barcoding_product from . import sales_order_koli from . import stock_backorder_confirmation diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index ff07c94c..191a44c9 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -161,86 +161,6 @@ class ResPartner(models.Model): "this feature", tracking=3) telegram_id = fields.Char(string="Telegram") - # MERCHANT - # informasi perusahaan - name_merchant = fields.Char(string='Name') - pejabat_name = fields.Char(string='Pejabat Name') - pic_merchant = fields.Char(string='PIC Merchant', required=True) - pic_position = fields.Char(string='Jabatan PIC') - address_merchant = fields.Char(string='Alamat') - state_merchant = fields.Many2one('res.country.state', string='State') - city_merchant = fields.Many2one('vit.kota', string='Kota') - district_merchant = fields.Many2one('vit.kecamatan', string='Kecamatan') - subDistrict_merchant = fields.Many2one('vit.kelurahan', string='Kelurahan') - zip_merchant = fields.Char(string='Kode Pos') - bank_name_merchant = fields.Char(string='Nama Bank') - rekening_name_merchant = fields.Char(string='Nama Rekening') - account_number_merchant = fields.Char(string='Nomor Rekening Bank') - email_company_merchant = fields.Char(string='Email Perusahaan') - email_sales_merchant = fields.Char(string='Email Sales') - email_finance_merchant = fields.Char(string='Email Finance') - phone_merchant = fields.Char(string='No. Telepon Perusahaan') - mobile_merchant = fields.Char(string='No. Handphone') - bisnis_type = fields.Selection([ - ('1', 'PT'), - ('2', 'CV'), - ('3', 'Perorangan'), - ]) - website_merchant = fields.Char(string='Website') - category_perusahaan = fields.Selection([ - ('1', 'Principal (Pemegang merk/Produsen)'), - ('2', 'Sole Distributor (Distributor Tunggal)'), - ('3', 'Authorized Distributor (Distributor Resmi)'), - ('4', 'Importer (Pengimpor Barang)'), - ('5', 'Wholesaler (Pedagang Besar)'), - ]) - # Informasi Vendor - harga_tayang = fields.Char(string='Harga Tayang (HET)') - category_produk_ids_merchant = fields.Many2many( - 'product.public.category', - string='Kategori Produk Merchant', - domain=lambda self: self._get_default_category_domain(), - relation='res_partner_category_produk_ids_merchant_rel' # Nama tabel relasi berbeda - ) - - @api.model - def _get_default_category_domain(self): - return [('parent_id', '=', False)] - - merk_dagang = fields.Char(string='Merk Dagang') - is_pengajuan_tempo = fields.Boolean(string='Apakah anda memiliki Form Pengajuan Tempo?') - tempo_duration_merchant = fields.Many2one('account.payment.term', string='Durasi Tempo') - kredit_limit = fields.Char(string='Kredit Limit') - waktu_pengiriman = fields.Char(string='Waktu Pengiriman') - terhitung_sejak = fields.Selection([ - ('1', 'Terima PO'), - ('2', 'Barang Dikirimkan'), - ('3', 'Tukar Faktur'), - ]) - - # syarat dagang - is_kembali_barang = fields.Char(string='Syarat Pengembalian Barang') - tenggat_waktu = fields.Char(string='Tenggat Waktu Perubahan Harga') - sertifikat_produk = fields.Char(string='Dokumen/Sertifikat yang Dimiliki Oleh Brand') - custom_sertifikat_produk = fields.Char(string='Dokumen/Sertifikat Lainnya') - tempo_garansi = fields.Selection([ - ('1', '6 Bulan Garansi'), - ('2', '1 Tahun Garansi'), - ('3', '2 Tahun Garansi'), - ]) - explain_garansi = fields.Char(string='Garansi Yang Dimaksudkan') - is_order_quantity = fields.Char(string='Apakah Memiliki Minimum Order Quantity (MOQ)') - - # dokumen - file_npwp = fields.Many2one('ir.attachment', string="NPWP Perusahaan", tracking=3) - file_sppkp = fields.Many2one('ir.attachment', string="SPPKP Perusahaan", tracking=3) - file_dokumenKtpDirut = fields.Many2one('ir.attachment', string="KTP Dirut/Direktur", tracking=3) - file_kartuNama = fields.Many2one('ir.attachment', string="Kartu Nama", tracking=3) - file_suratPernyataan = fields.Many2one('ir.attachment', string="Surat Pernyataan Nomor Rekening", tracking=3) - file_fotoKantor = fields.Many2one('ir.attachment', string="Foto Gudang / Kantor Bagian Depan", tracking=3) - file_dataProduk = fields.Many2one('ir.attachment', string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=3) - file_pricelist = fields.Many2one('ir.attachment', string="Pricelist", tracking=3) - @api.model def _default_payment_term(self): return self.env.ref('__export__.account_payment_term_26_484409e2').id @@ -409,60 +329,6 @@ class ResPartner(models.Model): vals['dokumen_foto_kantor'] = vals.get('dokumen_foto_kantor', self.dokumen_foto_kantor) vals['dokumen_tempat_bekerja'] = vals.get('dokumen_tempat_bekerja', self.dokumen_tempat_bekerja) - # MERCHANT - # Informasi Perusahaan - vals['name_merchant'] = vals.get('name_merchant', self.name_merchant) - vals['pejabat_name'] = vals.get('pejabat_name', self.pejabat_name) - vals['pic_merchant'] = vals.get('pic_merchant', self.pic_merchant) - vals['pic_position'] = vals.get('pic_position', self.pic_position) - vals['address_merchant'] = vals.get('address_merchant', self.address_merchant) - vals['state_merchant'] = vals.get('state_merchant', self.state_merchant) - vals['city_merchant'] = vals.get('city_merchant', self.city_merchant) - vals['district_merchant'] = vals.get('district_merchant', self.district_merchant) - vals['subDistrict_merchant'] = vals.get('subDistrict_merchant', self.subDistrict_merchant) - vals['zip_merchant'] = vals.get('zip_merchant', self.zip_merchant) - vals['bank_name_merchant'] = vals.get('bank_name_merchant', self.bank_name_merchant) - vals['rekening_name_merchant'] = vals.get('rekening_name_merchant', self.rekening_name_merchant) - vals['account_number_merchant'] = vals.get('account_number_merchant', self.account_number_merchant) - vals['email_company_merchant'] = vals.get('email_company_merchant', self.email_company_merchant) - vals['email_sales_merchant'] = vals.get('email_sales_merchant', self.email_sales_merchant) - vals['email_finance_merchant'] = vals.get('email_finance_merchant', self.email_finance_merchant) - vals['phone_merchant'] = vals.get('phone_merchant', self.phone_merchant) - vals['mobile_merchant'] = vals.get('mobile_merchant', self.mobile_merchant) - vals['bisnis_type'] = vals.get('bisnis_type', self.bisnis_type) - vals['website_merchant'] = vals.get('website_merchant', self.website_merchant) - vals['category_perusahaan'] = vals.get('category_perusahaan', self.category_perusahaan) - - # Informasi Vendor - vals['harga_tayang'] = vals.get('harga_tayang', self.harga_tayang) - vals['category_produk_ids_merchant'] = vals.get('category_produk_ids_merchant', self.category_produk_ids_merchant) - vals['merk_dagang'] = vals.get('merk_dagang', self.merk_dagang) - vals['is_pengajuan_tempo'] = vals.get('is_pengajuan_tempo', self.is_pengajuan_tempo) - vals['tempo_duration_merchant'] = vals.get('tempo_duration_merchant', self.tempo_duration_merchant) - vals['kredit_limit'] = vals.get('kredit_limit', self.kredit_limit) - vals['waktu_pengiriman'] = vals.get('waktu_pengiriman', self.waktu_pengiriman) - vals['terhitung_sejak'] = vals.get('terhitung_sejak', self.terhitung_sejak) - - # Syarat Dagang - vals['is_kembali_barang'] = vals.get('is_kembali_barang', self.is_kembali_barang) - vals['tenggat_waktu'] = vals.get('tenggat_waktu', self.tenggat_waktu) - vals['sertifikat_produk'] = vals.get('sertifikat_produk', self.sertifikat_produk) - vals['custom_sertifikat_produk'] = vals.get('custom_sertifikat_produk', self.custom_sertifikat_produk) - vals['tempo_garansi'] = vals.get('tempo_garansi', self.tempo_garansi) - vals['explain_garansi'] = vals.get('explain_garansi', self.explain_garansi) - vals['is_order_quantity'] = vals.get('is_order_quantity', self.is_order_quantity) - - # Dokumen - vals['file_dokumenKtpDirut'] = vals.get('file_dokumenKtpDirut', self.file_dokumenKtpDirut) - vals['file_kartuNama'] = vals.get('file_kartuNama', self.file_kartuNama) - vals['file_npwp'] = vals.get('file_npwp', self.file_npwp) - vals['file_sppkp'] = vals.get('file_sppkp', self.file_sppkp) - vals['file_suratPernyataan'] = vals.get('file_suratPernyataan', self.file_suratPernyataan) - vals['file_fotoKantor'] = vals.get('file_fotoKantor', self.file_fotoKantor) - vals['file_dataProduk'] = vals.get('file_dataProduk', self.file_dataProduk) - vals['file_pricelist'] = vals.get('file_pricelist', self.file_pricelist) - vals['description'] = vals.get('description', self.description) - # Simpan hanya field yang perlu di-update pada child vals_for_child = { 'customer_type': vals.get('customer_type'), @@ -534,55 +400,7 @@ class ResPartner(models.Model): 'dokumen_tempat_bekerja': vals.get('dokumen_tempat_bekerja'), # internal_notes - 'comment': vals.get('comment'), - - # Merchant - 'name_merchant': vals.get('name_merchant'), - 'pejabat_name': vals.get('pejabat_name'), - 'pic_merchant': vals.get('pic_merchant'), - 'pic_position': vals.get('pic_position'), - 'address_merchant': vals.get('address_merchant'), - 'state_merchant': vals.get('state_merchant'), - 'city_merchant': vals.get('city_merchant'), - 'district_merchant': vals.get('district_merchant'), - 'subDistrict_merchant': vals.get('subDistrict_merchant'), - 'zip_merchant': vals.get('zip_merchant'), - 'bank_name_merchant': vals.get('bank_name_merchant'), - 'rekening_name_merchant': vals.get('rekening_name_merchant'), - 'account_number_merchant': vals.get('account_number_merchant'), - 'email_company_merchant': vals.get('email_company_merchant'), - 'email_sales_merchant': vals.get('email_sales_merchant'), - 'email_finance_merchant': vals.get('email_finance_merchant'), - 'phone_merchant': vals.get('phone_merchant'), - 'mobile_merchant': vals.get('mobile_merchant'), - 'bisnis_type': vals.get('bisnis_type'), - 'website_merchant': vals.get('website_merchant'), - 'category_perusahaan': vals.get('category_perusahaan'), - 'harga_tayang': vals.get('harga_tayang'), - 'category_produk_ids_merchant': vals.get('category_produk_ids_merchant'), - 'merk_dagang': vals.get('merk_dagang'), - 'is_pengajuan_tempo': vals.get('is_pengajuan_tempo'), - 'tempo_duration_merchant': vals.get('tempo_duration_merchant'), - 'kredit_limit': vals.get('kredit_limit'), - 'waktu_pengiriman': vals.get('waktu_pengiriman'), - 'terhitung_sejak': vals.get('terhitung_sejak'), - 'is_kembali_barang': vals.get('is_kembali_barang'), - 'tenggat_waktu': vals.get('tenggat_waktu'), - 'sertifikat_produk': vals.get('sertifikat_produk'), - 'custom_sertifikat_produk': vals.get('custom_sertifikat_produk'), - 'tempo_garansi': vals.get('tempo_garansi'), - 'explain_garansi': vals.get('explain_garansi'), - 'is_order_quantity': vals.get('is_order_quantity'), - - 'file_dokumenKtpDirut': vals.get('file_dokumenKtpDirut'), - 'file_kartuNama': vals.get('file_kartuNama'), - 'file_npwp': vals.get('file_npwp'), - 'file_sppkp': vals.get('file_sppkp'), - 'file_suratPernyataan': vals.get('file_suratPernyataan'), - 'file_fotoKantor': vals.get('file_fotoKantor'), - 'file_dataProduk': vals.get('file_dataProduk'), - 'file_pricelist': vals.get('file_pricelist'), - 'description': vals.get('description'), + 'comment': vals.get('comment') } # Lakukan update pada semua child secara rekursif diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 6028a1d0..bdf8f1eb 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -336,12 +336,6 @@ class SaleOrder(models.Model): date_unhold = fields.Datetime(string='Date Unhold', tracking=True, readonly=True, help='Waktu ketika SO di Unhold' ) - ready_to_ship_status_detail = fields.Char( - string='Status Shipping Detail', - compute='_compute_ready_to_ship_status_detail' - ) - - def _compute_total_margin_excl_third_party(self): for order in self: if order.amount_untaxed == 0: @@ -2012,45 +2006,5 @@ class SaleOrder(models.Model): if any(fields in vals for fields in ['delivery_amt', 'carrier_id', 'shipping_cost_covered']): self._validate_delivery_amt() if any(field in vals for field in ["order_line", "client_order_ref"]): -<<<<<<< HEAD self._calculate_etrts_date() return res -======= - self._calculate_etrts_date() - return res - - # @api.depends('commitment_date') - def _compute_ready_to_ship_status_detail(self): - for order in self: - eta = order.commitment_date - - match_lines = self.env['purchase.order.sales.match'].search([ - ('sale_id', '=', order.id) - ]) - - if match_lines: - for match in match_lines: - po = match.purchase_order_id - product = match.product_id - - po_line = self.env['purchase.order.line'].search([ - ('order_id', '=', po.id), - ('product_id', '=', product.id) - ], limit=1) - - stock_move = self.env['stock.move'].search([ - ('purchase_line_id', '=', po_line.id) - ], limit=1) - picking_in = stock_move.picking_id - - result_date = picking_in.date_done if picking_in else None - if result_date: - status = "Early" if result_date < eta else "Delay" - result_date_str = result_date.strftime('%m/%d/%Y') - eta_str = eta.strftime('%m/%d/%Y') - order.ready_to_ship_status_detail = f"Expected: {eta_str} | Realtime: {result_date_str} | {status}" - else: - order.ready_to_ship_status_detail = "On Track" - else: - order.ready_to_ship_status_detail = 'On Track' ->>>>>>> CR/form-merchant diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py deleted file mode 100644 index a804e93f..00000000 --- a/indoteknik_custom/models/user_form_merchant.py +++ /dev/null @@ -1,98 +0,0 @@ -from odoo import models, fields, api -from odoo.exceptions import UserError -from odoo.http import request - - -class UserFormMerchant(models.Model): - _name = 'user.form.merchant' - _inherit = ['mail.thread', 'mail.activity.mixin'] - - name = fields.Char(string='Name') - # informasi peruhsaan - name_merchant = fields.Char(string='Name') - pejabat_name = fields.Char(string='Pejabat Name') - pic_merchant = fields.Char(string='PIC Merchant') - pic_position = fields.Char(string='Jabatan PIC') - partner_id = fields.Many2one('res.partner', string='Company') - address = fields.Char(string='Alamat') - state = fields.Many2one('res.country.state', string='State') - city = fields.Many2one('vit.kota', string='Kota') - district = fields.Many2one('vit.kecamatan', string='Kecamatan') - subDistrict = fields.Many2one('vit.kelurahan', string='Kelurahan') - zip = fields.Char(string='Kode Pos') - bank_name = fields.Char(string='Nama Bank') - rekening_name = fields.Char(string='Nama Rekening') - account_number = fields.Char(string='Nomor Rekening Bank') - email_company = fields.Char(string='Email Perusahaan') - email_sales = fields.Char(string='Email Sales') - email_finance = fields.Char(string='Email Finance') - phone = fields.Char(string='No. Telepon Perusahaan') - mobile = fields.Char(string='No. Handphone') - bisnis_type = fields.Selection([ - ('1', 'PT'), - ('2', 'CV'), - ('3', 'Perorangan'), - ]) - website = fields.Char(string='Website') - category_perusahaan = fields.Selection([ - ('1', 'Principal (Pemegang merk/Produsen)'), - ('2', 'Sole Distributor (Distributor Tunggal)'), - ('3', 'Authorized Distributor (Distributor Resmi)'), - ('4', 'Importer (Pengimpor Barang)'), - ('5', 'Wholesaler (Pedagang Besar)'), - ]) - description = fields.Text(string='Deskripsi') - - # imformasi Vendor - harga_tayang = fields.Char(string='Harga Tayang (HET)') - category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan', - domain=lambda self: self._get_default_category_domain()) - - @api.model - def _get_default_category_domain(self): - return [('parent_id', '=', False)] - - merk_dagang = fields.Char(string='Merk Dagang') - is_pengajuan_tempo = fields.Boolean(string='Apakah anda memiliki Form Pengajuan Tempo?') - tempo_duration = fields.Many2one('account.payment.term', string='Durasi Tempo') - kredit_limit = fields.Char(string='Kredit Limit') - waktu_pengiriman = fields.Char(string='Waktu Pengiriman') - terhitung_sejak = fields.Selection([ - ('1', 'Terima PO'), - ('2', 'Barang Dikirimkan'), - ('3', 'Tukar Faktur'), - ]) - - # syarat dagang - is_kembali_barang = fields.Char(string='Syarat Pengembalian Barang') - tenggat_waktu = fields.Char(string='Tenggat Waktu Perubahan Harga') - sertifikat_produk = fields.Char(string='Dokumen/Sertifikat yang Dimiliki Oleh Brand') - custom_sertifikat_produk = fields.Char(string='Dokumen/Sertifikat Lainnya') - tempo_garansi = fields.Selection([ - ('1', '6 Bulan Garansi'), - ('2', '1 Tahun Garansi'), - ('3', '2 Tahun Garansi'), - ]) - explain_garansi = fields.Char(string='Garansi Yang Dimaksudkan') - is_order_quantity = fields.Char(string='Apakah Memiliki Minimum Order Quantity (MOQ)') - - # dokumen - file_npwp = fields.Many2one('ir.attachment', string="NPWP Perusahaan", tracking=3) - file_sppkp = fields.Many2one('ir.attachment', string="SPPKP Perusahaan", tracking=3) - file_dokumenKtpDirut = fields.Many2one('ir.attachment', string="KTP Dirut/Direktur", tracking=3) - file_kartuNama = fields.Many2one('ir.attachment', string="Kartu Nama", tracking=3) - file_suratPernyataan = fields.Many2one('ir.attachment', string="Surat Pernyataan Nomor Rekening", tracking=3) - file_fotoKantor = fields.Many2one('ir.attachment', string="Foto Gudang / Kantor Bagian Depan", tracking=3) - file_dataProduk = fields.Many2one('ir.attachment', string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=3) - file_pricelist = fields.Many2one('ir.attachment', string="Pricelist", tracking=3) - - @api.depends('name', 'name_merchant') - def name_get(self): - result = [] - for record in self: - if record.name_merchant: - display_name = record.name_merchant - else: - display_name = "DETAIL FORM MERCHANT" - result.append((record.id, display_name)) - return result \ No newline at end of file diff --git a/indoteknik_custom/models/user_merchant_request.py b/indoteknik_custom/models/user_merchant_request.py deleted file mode 100644 index dd571cdc..00000000 --- a/indoteknik_custom/models/user_merchant_request.py +++ /dev/null @@ -1,125 +0,0 @@ -from odoo import models, fields, api, _ -from odoo.exceptions import UserError -from odoo.http import request - - -class RejectReasonWizardMerchant(models.TransientModel): - _name = 'reject.reason.wizard.merchant' - _description = 'Wizard for Reject Reason' - - request_id = fields.Many2one('user.merchant.request', string='Request') - reason_reject = fields.Text(string='Reason for Rejection', required=True) - - def confirm_reject(self): - merchant = self.request_id - if merchant: - merchant.write({'reason_reject': self.reason_reject}) - merchant.state_merchant = 'reject' - return {'type': 'ir.actions.act_window_close'} - - -class ConfirmApprovalWizardMerchant(models.TransientModel): - _name = 'confirm.approval.wizard.merchant' - _description = 'Wizard Konfirmasi Approval' - - merchant_id = fields.Many2one('user.merchant.request', string='Merchant', required=True) - - def confirm_approval(self): - merchant = self.merchant_id - if merchant.state_merchant == 'draft': - merchant.state_merchant = 'approved' - - -class UserMerchantRequest(models.Model): - _name = 'user.merchant.request' - _inherit = ['mail.thread', 'mail.activity.mixin'] - _rec_name = 'user_id' - - user_id = fields.Many2one('res.partner', string='User') - merchant_id = fields.Many2one('user.form.merchant', string='Form Merchant') - user_company_id = fields.Many2one('res.partner', string='Company') - state_merchant = fields.Selection([ - ('draft', 'Pengajuan Merchant'), - ('approved', 'Approved Merchant'), - ('reject', 'Rejected'), - ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft') - reason_reject = fields.Char(string='Reaject Reason') - - def button_approve(self): - for merchant in self: - return { - 'type': 'ir.actions.act_window', - 'name': 'Konfirmasi Approve', - 'res_model': 'confirm.approval.wizard.merchant', - 'view_mode': 'form', - 'target': 'new', - 'context': { - 'default_merchant_id': merchant.id, - }} - - def button_reject(self): - return { - 'type': 'ir.actions.act_window', - 'name': _('Reject Reason'), - 'res_model': 'reject.reason.wizard.merchant', - 'view_mode': 'form', - 'target': 'new', - 'context': {'default_request_id': self.id}, - } - - def write(self, vals): - is_approve = True if self.state_merchant == 'approved' or vals.get('state_merchant') == 'approved' else False - if is_approve: - # Informasi Perusahaan - self.user_company_id.name_merchant = self.merchant_id.name_merchant - self.user_company_id.pejabat_name = self.merchant_id.pejabat_name - self.user_company_id.pic_merchant = self.merchant_id.pic_merchant - self.user_company_id.pic_position = self.merchant_id.pic_position - self.user_company_id.address_merchant = self.merchant_id.address - self.user_company_id.state_merchant = self.merchant_id.state - self.user_company_id.city_merchant = self.merchant_id.city - self.user_company_id.district_merchant = self.merchant_id.district - self.user_company_id.subDistrict_merchant = self.merchant_id.subDistrict - self.user_company_id.zip_merchant = self.merchant_id.zip - self.user_company_id.bank_name_merchant = self.merchant_id.bank_name - self.user_company_id.rekening_name_merchant = self.merchant_id.rekening_name - self.user_company_id.account_number_merchant = self.merchant_id.account_number - self.user_company_id.email_company_merchant = self.merchant_id.email_company - self.user_company_id.email_sales_merchant = self.merchant_id.email_sales - self.user_company_id.email_finance_merchant = self.merchant_id.email_finance - self.user_company_id.phone_merchant = self.merchant_id.phone - self.user_company_id.mobile_merchant = self.merchant_id.mobile - self.user_company_id.bisnis_type = self.merchant_id.bisnis_type - self.user_company_id.website_merchant = self.merchant_id.website - self.user_company_id.category_perusahaan = self.merchant_id.category_perusahaan - - # Informasi Vendor - self.user_company_id.harga_tayang = self.merchant_id.harga_tayang - self.user_company_id.category_produk_ids_merchant = self.merchant_id.category_produk_ids - self.user_company_id.merk_dagang = self.merchant_id.merk_dagang - self.user_company_id.is_pengajuan_tempo = self.merchant_id.is_pengajuan_tempo - self.user_company_id.tempo_duration_merchant = self.merchant_id.tempo_duration - self.user_company_id.kredit_limit = self.merchant_id.kredit_limit - self.user_company_id.waktu_pengiriman = self.merchant_id.waktu_pengiriman - self.user_company_id.terhitung_sejak = self.merchant_id.terhitung_sejak - - # Syarat Perdagangan - self.user_company_id.is_kembali_barang = self.merchant_id.is_kembali_barang - self.user_company_id.tenggat_waktu = self.merchant_id.tenggat_waktu - self.user_company_id.sertifikat_produk = self.merchant_id.sertifikat_produk - self.user_company_id.tempo_garansi = self.merchant_id.tempo_garansi - self.user_company_id.explain_garansi = self.merchant_id.explain_garansi - self.user_company_id.is_order_quantity = self.merchant_id.is_order_quantity - - # Dokumen - self.user_company_id.file_npwp = self.merchant_id.file_npwp - self.user_company_id.file_sppkp = self.merchant_id.file_sppkp - self.user_company_id.file_dokumenKtpDirut = self.merchant_id.file_dokumenKtpDirut - self.user_company_id.file_kartuNama = self.merchant_id.file_kartuNama - self.user_company_id.file_suratPernyataan = self.merchant_id.file_suratPernyataan - self.user_company_id.file_fotoKantor = self.merchant_id.file_fotoKantor - self.user_company_id.file_dataProduk = self.merchant_id.file_dataProduk - self.user_company_id.file_pricelist = self.merchant_id.file_pricelist - self.user_company_id.description = self.merchant_id.description - - return super(UserMerchantRequest, self).write(vals) \ No newline at end of file -- cgit v1.2.3 From d27999cacee59a115ae3c6c46542c2e20cfe176b Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 09:42:03 +0700 Subject: bf cant create invoice dp cause of custom method action assign stock picking --- indoteknik_custom/models/stock_picking.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0071bb1a..0fcb7ca1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1012,15 +1012,15 @@ class StockPicking(models.Model): self.sale_id.date_doc_kirim = self.date_doc_kirim def action_assign(self): - if self.sale_id.hold_outgoing and self.location_id.id == 57 and self.location_dest_id.id == 60: - print(1) - else: - res = super(StockPicking, self).action_assign() + res = super(StockPicking, self).action_assign() + for move in self: + # if not move.sale_id.hold_outgoing and move.location_id.id != 57 and move.location_dest_id.id != 60: + # TODO cant skip hold outgoing cause of not singleton method current_time = datetime.datetime.utcnow() - self.real_shipping_id = self.sale_id.real_shipping_id - self.date_availability = current_time + move.real_shipping_id = move.sale_id.real_shipping_id + move.date_availability = current_time # self.check_state_reserve() - return res + return res def ask_approval(self): if self.env.user.is_accounting: -- cgit v1.2.3 From 6c91d8d4973eb4588bb2f7b7cbb8cd4b6c59baac Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 23 May 2025 11:03:57 +0700 Subject: fix expected ready to ship --- indoteknik_custom/models/sale_order.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bdf8f1eb..f89dfb10 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -331,6 +331,10 @@ class SaleOrder(models.Model): ('hold', 'Hold'), ('approve', 'Approve') ], tracking=True, string='State Cancel', copy=False) + ready_to_ship_status_detail = fields.Char( + string='Status Shipping Detail', + compute='_compute_ready_to_ship_status_detail' + ) date_hold = fields.Datetime(string='Date Hold', tracking=True, readonly=True, help='Waktu ketika SO di Hold' ) date_unhold = fields.Datetime(string='Date Unhold', tracking=True, readonly=True, help='Waktu ketika SO di Unhold' @@ -2008,3 +2012,33 @@ class SaleOrder(models.Model): if any(field in vals for field in ["order_line", "client_order_ref"]): self._calculate_etrts_date() return res + + # @api.depends('commitment_date') + def _compute_ready_to_ship_status_detail(self): + for order in self: + eta = order.commitment_date + match_lines = self.env['purchase.order.sales.match'].search([ + ('sale_id', '=', order.id) + ]) + if match_lines: + for match in match_lines: + po = match.purchase_order_id + product = match.product_id + po_line = self.env['purchase.order.line'].search([ + ('order_id', '=', po.id), + ('product_id', '=', product.id) + ], limit=1) + stock_move = self.env['stock.move'].search([ + ('purchase_line_id', '=', po_line.id) + ], limit=1) + picking_in = stock_move.picking_id + result_date = picking_in.date_done if picking_in else None + if result_date: + status = "Early" if result_date < eta else "Delay" + result_date_str = result_date.strftime('%m/%d/%Y') + eta_str = eta.strftime('%m/%d/%Y') + order.ready_to_ship_status_detail = f"Expected: {eta_str} | Realtime: {result_date_str} | {status}" + else: + order.ready_to_ship_status_detail = "On Track" + else: + order.ready_to_ship_status_detail = 'On Track' \ No newline at end of file -- cgit v1.2.3 From 019bba2a3ff2ed08f53200df02c0638ddabbbe22 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 11:16:30 +0700 Subject: bf outgoing with hold qty and change formula of reordering rule --- indoteknik_custom/models/automatic_purchase.py | 2 +- indoteknik_custom/models/product_template.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 4feec307..1d7c5e31 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -486,7 +486,7 @@ class AutomaticPurchase(models.Model): # _logger.info('test %s' % point.product_id.name) if point.product_id.qty_available_bandengan > point.product_min_qty: continue - qty_purchase = point.product_max_qty - point.product_id.qty_available_bandengan + qty_purchase = point.product_max_qty - point.product_id.qty_incoming_bandengan - point.product_id.qty_onhand_bandengan po_line = self.env['purchase.order.line'].search([('product_id', '=', point.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) if self.vendor_id: diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 34b8fd2c..c62ca63e 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -1116,7 +1116,8 @@ class ProductProduct(models.Model): domain=[ ('product_id', '=', product.id), ('location_id', 'in', [57, 83]), - ('mo_id', '=', False) + ('mo_id', '=', False), + ('hold_outgoing', '=', False) ], fields=['qty_need'], groupby=[] @@ -1359,6 +1360,7 @@ class OutstandingMove(models.Model): location_id = fields.Many2one('stock.location', string='Location', help='Lokasi asal') location_dest_id = fields.Many2one('stock.location', string='Location To', help='Lokasi tujuan') mo_id = fields.Many2one('mrp.production', string='Manufacturing Order') + hold_outgoing = fields.Boolean(string='Hold Outgoing') def init(self): # where clause 'state in' follow the origin of outgoing and incoming odoo @@ -1368,8 +1370,11 @@ class OutstandingMove(models.Model): select sm.id, sm.reference, sm.product_id, sm.product_uom_qty as qty_need, sm.location_id, sm.location_dest_id, - sm.raw_material_production_id as mo_id + sm.raw_material_production_id as mo_id, + so.hold_outgoing from stock_move sm + left join procurement_group pg on pg.id = sm.group_id + left join sale_order so on so.id = pg.sale_id where 1=1 and sm.state in( 'waiting', -- cgit v1.2.3 From abc587069b2526756b4848b53f1d20a8b5825134 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 23 May 2025 12:29:01 +0700 Subject: overwrite function --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index 565b0315..8c2d0d94 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -682,6 +682,9 @@ class UserPengajuanTempoRequest(models.Model): ('active', 'in', [True, False]) ]) + def _message_get_suggested_recipients(self): + return {} + def format_currency(self, number): number = int(number) return "{:,}".format(number).replace(',', '.') \ No newline at end of file -- cgit v1.2.3 From 87e533be1993fd6730ead008e6fdd676d48c2bb7 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 23 May 2025 14:15:10 +0700 Subject: fix bug website checkout purchase_tax_id --- indoteknik_custom/models/sale_order_line.py | 33 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 049bee2d..753e3e29 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -255,32 +255,33 @@ class SaleOrderLine(models.Model): def _get_valid_purchase_price(self, purchase_price): current_time = datetime.now() delta_time = current_time - timedelta(days=365) + default_timestamp = datetime(1970, 1, 1, 0, 0, 0) # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') price = 0 - taxes = '' + taxes = 24 vendor_id = '' human_last_update = purchase_price.human_last_update or datetime.min system_last_update = purchase_price.system_last_update or datetime.min - if purchase_price.taxes_product_id.type_tax_use == 'purchase': - price = purchase_price.product_price - taxes = purchase_price.taxes_product_id.id + #if purchase_price.taxes_product_id.type_tax_use == 'purchase': + price = purchase_price.product_price + taxes = purchase_price.taxes_product_id.id or 24 + vendor_id = purchase_price.vendor_id.id + if delta_time > human_last_update: + price = 0 + taxes = 24 + vendor_id = '' + + if system_last_update > human_last_update: + #if purchase_price.taxes_system_id.type_tax_use == 'purchase': + price = purchase_price.system_price + taxes = purchase_price.taxes_system_id.id or 24 vendor_id = purchase_price.vendor_id.id - if delta_time > human_last_update: + if delta_time > system_last_update: price = 0 - taxes = '' + taxes = 24 vendor_id = '' - - if system_last_update > human_last_update: - if purchase_price.taxes_system_id.type_tax_use == 'purchase': - price = purchase_price.system_price - taxes = purchase_price.taxes_system_id.id - vendor_id = purchase_price.vendor_id.id - if delta_time > system_last_update: - price = 0 - taxes = '' - vendor_id = '' return price, taxes, vendor_id -- cgit v1.2.3 From 8f5b6e060c8dd08f01476ac5469faa6fce706c55 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 14:16:57 +0700 Subject: bf cant get purchase price cause of null in purchase tax --- indoteknik_custom/models/sale_order_line.py | 35 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 049bee2d..3c4965e5 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -255,33 +255,34 @@ class SaleOrderLine(models.Model): def _get_valid_purchase_price(self, purchase_price): current_time = datetime.now() delta_time = current_time - timedelta(days=365) + default_timestamp = datetime(1970, 1, 1, 0, 0, 0) # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') price = 0 - taxes = '' + taxes = 24 vendor_id = '' human_last_update = purchase_price.human_last_update or datetime.min system_last_update = purchase_price.system_last_update or datetime.min - - if purchase_price.taxes_product_id.type_tax_use == 'purchase': - price = purchase_price.product_price - taxes = purchase_price.taxes_product_id.id + + # if purchase_price.taxes_product_id.type_tax_use == 'purchase': + price = purchase_price.product_price + taxes = purchase_price.taxes_product_id.id or 24 + vendor_id = purchase_price.vendor_id.id + if delta_time > human_last_update: + price = 0 + taxes = 24 + vendor_id = '' + + if system_last_update > human_last_update: + # if purchase_price.taxes_system_id.type_tax_use == 'purchase': + price = purchase_price.system_price + taxes = purchase_price.taxes_system_id.id or 24 vendor_id = purchase_price.vendor_id.id - if delta_time > human_last_update: + if delta_time > system_last_update: price = 0 - taxes = '' + taxes = 24 vendor_id = '' - if system_last_update > human_last_update: - if purchase_price.taxes_system_id.type_tax_use == 'purchase': - price = purchase_price.system_price - taxes = purchase_price.taxes_system_id.id - vendor_id = purchase_price.vendor_id.id - if delta_time > system_last_update: - price = 0 - taxes = '' - vendor_id = '' - return price, taxes, vendor_id @api.onchange('product_id') -- cgit v1.2.3 From 936c6cf2c1aea0e2b470d20d6fd41a10380bcac0 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 14:18:15 +0700 Subject: bf cant get purchase price cause of null in tax --- indoteknik_custom/models/sale_order_line.py | 35 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 049bee2d..3c4965e5 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -255,33 +255,34 @@ class SaleOrderLine(models.Model): def _get_valid_purchase_price(self, purchase_price): current_time = datetime.now() delta_time = current_time - timedelta(days=365) + default_timestamp = datetime(1970, 1, 1, 0, 0, 0) # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') price = 0 - taxes = '' + taxes = 24 vendor_id = '' human_last_update = purchase_price.human_last_update or datetime.min system_last_update = purchase_price.system_last_update or datetime.min - - if purchase_price.taxes_product_id.type_tax_use == 'purchase': - price = purchase_price.product_price - taxes = purchase_price.taxes_product_id.id + + # if purchase_price.taxes_product_id.type_tax_use == 'purchase': + price = purchase_price.product_price + taxes = purchase_price.taxes_product_id.id or 24 + vendor_id = purchase_price.vendor_id.id + if delta_time > human_last_update: + price = 0 + taxes = 24 + vendor_id = '' + + if system_last_update > human_last_update: + # if purchase_price.taxes_system_id.type_tax_use == 'purchase': + price = purchase_price.system_price + taxes = purchase_price.taxes_system_id.id or 24 vendor_id = purchase_price.vendor_id.id - if delta_time > human_last_update: + if delta_time > system_last_update: price = 0 - taxes = '' + taxes = 24 vendor_id = '' - if system_last_update > human_last_update: - if purchase_price.taxes_system_id.type_tax_use == 'purchase': - price = purchase_price.system_price - taxes = purchase_price.taxes_system_id.id - vendor_id = purchase_price.vendor_id.id - if delta_time > system_last_update: - price = 0 - taxes = '' - vendor_id = '' - return price, taxes, vendor_id @api.onchange('product_id') -- cgit v1.2.3 From 76d5328565d394c5e78c56c7c2fc37e5470022ce Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Fri, 23 May 2025 15:06:29 +0700 Subject: refactor get valid purchase price cause of null in tax purchase --- indoteknik_custom/models/automatic_purchase.py | 14 +++++------ indoteknik_custom/models/mrp_production.py | 32 +++++++++++++------------- indoteknik_custom/models/requisition.py | 14 +++++------ 3 files changed, 30 insertions(+), 30 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index b66121e1..c5cc686a 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -589,18 +589,18 @@ class AutomaticPurchaseLine(models.Model): def _get_valid_purchase_price(self, purchase_price): price = 0 - taxes = '' + taxes = 24 human_last_update = purchase_price.human_last_update or datetime.min system_last_update = purchase_price.system_last_update or datetime.min - if purchase_price.taxes_product_id.type_tax_use == 'purchase': - price = purchase_price.product_price - taxes = purchase_price.taxes_product_id.id + #if purchase_price.taxes_product_id.type_tax_use == 'purchase': + price = purchase_price.product_price + taxes = purchase_price.taxes_product_id.id or 24 if system_last_update > human_last_update: - if purchase_price.taxes_system_id.type_tax_use == 'purchase': - price = purchase_price.system_price - taxes = purchase_price.taxes_system_id.id + #if purchase_price.taxes_system_id.type_tax_use == 'purchase': + price = purchase_price.system_price + taxes = purchase_price.taxes_system_id.id or 24 return price, taxes diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py index 58c2512c..14821f27 100644 --- a/indoteknik_custom/models/mrp_production.py +++ b/indoteknik_custom/models/mrp_production.py @@ -180,29 +180,29 @@ class MrpProduction(models.Model): # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S') price = 0 - taxes = '' + taxes = 24 vendor_id = '' human_last_update = purchase_price.human_last_update or datetime.min system_last_update = purchase_price.system_last_update or datetime.min - if purchase_price.taxes_product_id.type_tax_use == 'purchase': - price = purchase_price.product_price - taxes = purchase_price.taxes_product_id.id + #if purchase_price.taxes_product_id.type_tax_use == 'purchase': + price = purchase_price.product_price + taxes = purchase_price.taxes_product_id.id or 24 + vendor_id = purchase_price.vendor_id.id + if delta_time > human_last_update: + price = 0 + taxes = '' + vendor_id = '' + + if system_last_update > human_last_update: + #if purchase_price.taxes_system_id.type_tax_use == 'purchase': + price = purchase_price.system_price + taxes = purchase_price.taxes_system_id.id or 24 vendor_id = purchase_price.vendor_id.id - if delta_time > human_last_update: + if delta_time > system_last_update: price = 0 - taxes = '' + taxes = 24 vendor_id = '' - - if system_last_update > human_last_update: - if purchase_price.taxes_system_id.type_tax_use == 'purchase': - price = purchase_price.system_price - taxes = purchase_price.taxes_system_id.id - vendor_id = purchase_price.vendor_id.id - if delta_time > system_last_update: - price = 0 - taxes = '' - vendor_id = '' return price, taxes, vendor_id diff --git a/indoteknik_custom/models/requisition.py b/indoteknik_custom/models/requisition.py index 1d350929..74236850 100644 --- a/indoteknik_custom/models/requisition.py +++ b/indoteknik_custom/models/requisition.py @@ -299,18 +299,18 @@ class RequisitionLine(models.Model): def _get_valid_purchase_price(self, purchase_price): price = 0 - taxes = '' + taxes = 24 human_last_update = purchase_price.human_last_update or datetime.min system_last_update = purchase_price.system_last_update or datetime.min - if purchase_price.taxes_product_id.type_tax_use == 'purchase': - price = purchase_price.product_price - taxes = purchase_price.taxes_product_id.id + #if purchase_price.taxes_product_id.type_tax_use == 'purchase': + price = purchase_price.product_price + taxes = purchase_price.taxes_product_id.id or 24 if system_last_update > human_last_update: - if purchase_price.taxes_system_id.type_tax_use == 'purchase': - price = purchase_price.system_price - taxes = purchase_price.taxes_system_id.id + #if purchase_price.taxes_system_id.type_tax_use == 'purchase': + price = purchase_price.system_price + taxes = purchase_price.taxes_system_id.id or 24 return price, taxes -- cgit v1.2.3 From 64c86581b469e962789ea6a7ea45c17ed020fe9a Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Sat, 24 May 2025 11:23:38 +0700 Subject: add putra to role sales manager in user pengajuan tempo request --- indoteknik_custom/models/user_pengajuan_tempo_request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index 565b0315..aae09cc4 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -365,13 +365,13 @@ class UserPengajuanTempoRequest(models.Model): @api.onchange('tempo_duration') def _tempo_duration_change(self): for tempo in self: - if tempo.env.user.id not in (7, 688, 28, 377, 12182): + if tempo.env.user.id not in (7, 688, 28, 377, 12182, 375): raise UserError("Durasi tempo hanya bisa di ubah oleh Sales Manager atau Direktur") @api.onchange('tempo_limit') def _onchange_tempo_limit(self): for tempo in self: - if tempo.env.user.id not in (7, 688, 28, 377, 12182): + if tempo.env.user.id not in (7, 688, 28, 377, 12182, 375): raise UserError("Limit tempo hanya bisa diubah oleh Sales Manager atau Direktur") def button_approve(self): for tempo in self: @@ -381,7 +381,7 @@ class UserPengajuanTempoRequest(models.Model): if tempo.env.user.id in (688, 28, 7): raise UserError("Pengajuan tempo harus di approve oleh sales manager terlebih dahulu") else: - if tempo.env.user.id not in (377, 12182): + if tempo.env.user.id not in (377, 12182, 375): # if tempo.env.user.id != 12182: raise UserError("Pengajuan tempo hanya bisa di approve oleh sales manager") else: -- cgit v1.2.3 From 0ba6b5b041d63adc6d5f3bc69c001d450c75742e Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 26 May 2025 08:01:18 +0700 Subject: (andri) set default urutan tabel invoices pada dunning run --- indoteknik_custom/models/dunning_run.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index c167aab7..bb53fc0c 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -19,7 +19,7 @@ class DunningRun(models.Model): partner_id = fields.Many2one( 'res.partner', string='Customer', required=True, change_default=True, index=True, tracking=1) - dunning_line = fields.One2many('dunning.run.line', 'dunning_id', string='Dunning Lines', auto_join=True) + dunning_line = fields.One2many('dunning.run.line', 'dunning_id', string='Dunning Lines', auto_join=True, order='invoice_id desc') # dunning_level = fields.Integer(string='Dunning Level', default=30, help='30 hari sebelum jatuh tempo invoice') date_kirim_tukar_faktur = fields.Date(string='Kirim Faktur') resi_tukar_faktur = fields.Char(string='Resi Faktur') @@ -122,7 +122,8 @@ class DunningRun(models.Model): class DunningRunLine(models.Model): _name = 'dunning.run.line' _description = 'Dunning Run Line' - _order = 'dunning_id, id' + # _order = 'dunning_id, id' + _order = 'invoice_id desc, id' dunning_id = fields.Many2one('dunning.run', string='Dunning Ref', required=True, ondelete='cascade', index=True, copy=False) partner_id = fields.Many2one('res.partner', string='Customer') -- cgit v1.2.3 From bab061bc003f132e738d7ad2f9d99df903392d1a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 26 May 2025 08:12:58 +0700 Subject: (andri) revisi log note product variant --- indoteknik_custom/models/product_template.py | 160 +++++++++++---------------- 1 file changed, 66 insertions(+), 94 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 5cb3da88..5480204f 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -797,7 +797,7 @@ class ProductTemplate(models.Model): self.message_post(body=f"Vendor Pricelist: all removed") def _log_field_changes_product(self, vals, old_values): - """Log general field changes for product template""" + """Revised - Log general field changes for product template without posting to variants""" exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] image_fields = ['image_1920', 'image_carousel_lines', 'product_template_image_ids'] @@ -867,13 +867,9 @@ class ProductTemplate(models.Model): changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") if changes: + # PERBAIKAN: Hanya post ke template, HAPUS bagian log ke variants record.message_post(body=f"Updated:
      {''.join(changes)}
    ") - # log changes to product variants - # variant_message = f"Updated:
      {''.join(changes)}
    " - # for variant in record.product_variant_ids: - # variant.message_post(body=variant_message) - # simpan data lama dan log perubahan field def write(self, vals): context = self._get_context_with_all_info(vals) @@ -1222,101 +1218,77 @@ class ProductProduct(models.Model): # log perubahan field def _log_field_changes_product_variants(self, vals, old_values): - exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] - # for image fields, use custom labels - custom_labels = { - 'image_1920': 'Main Image', - 'image_carousel_lines': 'Carousel Images', - 'product_template_image_ids': 'Extra Product Media', - } - - template_changes = {} + """Revised - Log field changes for variants without posting to template""" + exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] + + # Custom labels for image fields + custom_labels = { + 'image_1920': 'Main Image', + 'image_carousel_lines': 'Carousel Images', + 'product_template_image_ids': 'Extra Product Media', + } - for record in self: - changes = [] - for field_name in vals: - if field_name not in record._fields or field_name in exclude_fields: - continue + for record in self: + changes = [] + for field_name in vals: + if field_name not in record._fields or field_name in exclude_fields: + continue - field = record._fields[field_name] - field_label = custom_labels.get(field_name, field.string or field_name) - old_value = old_values.get(record.id, {}).get(field_name) - new_value = record[field_name] # nilai setelah write + field = record._fields[field_name] + field_label = custom_labels.get(field_name, field.string or field_name) + old_value = old_values.get(record.id, {}).get(field_name) + new_value = record[field_name] - def stringify(val, field, record): - if val in [None, False]: - return 'None' - if isinstance(field, fields.Selection): - selection = field.selection - if callable(selection): - selection = selection(record) - return dict(selection).get(val, str(val)) - if isinstance(field, fields.Boolean): - return 'Yes' if val else 'No' - if isinstance(field, fields.Many2one): - if isinstance(val, int): - rec = record.env[field.comodel_name].browse(val) - return rec.display_name if rec.exists() else str(val) - elif isinstance(val, models.BaseModel): - return val.display_name - return str(val) - if isinstance(field, fields.Many2many): - records = val if isinstance(val, models.BaseModel) else record[field.name] - if not records: - return 'None' - for attr in ['name', 'x_name', 'display_name']: - if hasattr(records[0], attr): - return ", ".join(records.mapped(attr)) - return ", ".join(str(r.id) for r in records) - if isinstance(field, fields.One2many): - records = val if isinstance(val, models.BaseModel) else record[field.name] - if not records: - return 'None' - return f"{field.comodel_name}({', '.join(str(r.id) for r in records)})" + def stringify(val, field, record): + if val in [None, False]: + return 'None' + if isinstance(field, fields.Selection): + selection = field.selection + if callable(selection): + selection = selection(record) + return dict(selection).get(val, str(val)) + if isinstance(field, fields.Boolean): + return 'Yes' if val else 'No' + if isinstance(field, fields.Many2one): + if isinstance(val, int): + rec = record.env[field.comodel_name].browse(val) + return rec.display_name if rec.exists() else str(val) + elif isinstance(val, models.BaseModel): + return val.display_name return str(val) + if isinstance(field, fields.Many2many): + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + return ", ".join(records.mapped(attr)) + return ", ".join(str(r.id) for r in records) + if isinstance(field, fields.One2many): + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' + return f"{field.comodel_name}({', '.join(str(r.id) for r in records)})" + return str(val) - old_val_str = stringify(old_value, field, record) - new_val_str = stringify(new_value, field, record) - - if old_val_str != new_val_str: - if field_name in custom_labels: # handle image field - if old_val_str == 'None' and new_val_str != 'None': - changes.append(f"
  • {field_label}: image added
  • ") - elif old_val_str != 'None' and new_val_str == 'None': - changes.append(f"
  • {field_label}: image removed
  • ") - else: - changes.append(f"
  • {field_label}: image updated
  • ") - else: - changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + old_val_str = stringify(old_value, field, record) + new_val_str = stringify(new_value, field, record) - if changes: - # Post message to variant - variant_message = "Updated:
      %s
    " % "".join(changes) - record.message_post(body=variant_message) - - # Group changes by template for posting to template - template_id = record.product_tmpl_id.id - if template_id not in template_changes: - template_changes[template_id] = {} - - # Store variant information including ID for creating clickable link - template_changes[template_id][record.id] = { - 'name': record.display_name or f"Variant {record.id}", - 'changes': changes - } + if old_val_str != new_val_str: + if field_name in custom_labels: # handle image field + if old_val_str == 'None' and new_val_str != 'None': + changes.append(f"
  • {field_label}: image added
  • ") + elif old_val_str != 'None' and new_val_str == 'None': + changes.append(f"
  • {field_label}: image removed
  • ") + else: + changes.append(f"
  • {field_label}: image updated
  • ") + else: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") - # Post grouped messages to templates with clickable links using your format - for template_id, variants_data in template_changes.items(): - template = self.env['product.template'].browse(template_id) - if template.exists(): - template_message = "Variant Updates:
    " - - for variant_id, variant_data in variants_data.items(): - # Create clickable link using your format - variant_link = f"{variant_data['name']}
    " - template_message += f"{variant_link}
      {''.join(variant_data['changes'])}

    " - - template.message_post(body=template_message) + if changes: + # PERBAIKAN: Hanya post message ke variant, HAPUS bagian template_changes + variant_message = "Updated:
      %s
    " % "".join(changes) + record.message_post(body=variant_message) # simpan data lama dan log perubahan field def write(self, vals): -- cgit v1.2.3 From 8fefc2e71c5e8a9f040f2fe6c9310b20cd88db3f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 26 May 2025 09:00:23 +0700 Subject: fix approval invoice date --- indoteknik_custom/models/approval_invoice_date.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/approval_invoice_date.py b/indoteknik_custom/models/approval_invoice_date.py index b58d55d4..e1bc4c9b 100644 --- a/indoteknik_custom/models/approval_invoice_date.py +++ b/indoteknik_custom/models/approval_invoice_date.py @@ -32,6 +32,7 @@ class ApprovalInvoiceDate(models.Model): if not self.env.user.is_accounting: raise UserError("Hanya Accounting Yang Bisa Approve") self.move_id.invoice_date = self.date_doc_do.date() + self.picking_id.date_doc_kirim = self.date_doc_do self.state = 'done' self.approve_date = datetime.utcnow() self.approve_by = self.env.user.id -- cgit v1.2.3 From 8ac8c2f8b89e6069bf13e879d486fa9cf4814cb4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 27 May 2025 09:02:47 +0700 Subject: push cr shipment group, vendor approval and purchase order --- indoteknik_custom/models/purchase_order.py | 18 +++++++++++++++--- indoteknik_custom/models/shipment_group.py | 8 ++++++++ indoteknik_custom/models/vendor_approval.py | 6 +++--- 3 files changed, 26 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index c5ba5792..cbfd4acd 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -827,11 +827,23 @@ class PurchaseOrder(models.Model): self.check_different_vendor_so_po() # self.check_data_vendor() - if self.amount_untaxed >= 50000000 and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): - raise UserError("Hanya Merchandiser yang bisa approve") + if self.amount_untaxed >= 50000000 and not self.env.user.id == 21: + raise UserError("Hanya Rafly Hanggara yang bisa approve") if self.total_percent_margin < self.total_so_percent_margin and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and not self.env.user.is_leader: - raise UserError("Beda Margin dengan Sales, harus approval Merchandise") + self.env.user.notify_danger( + title='WARNING!!!', + message='Beda Margin dengan Sale Order', + sticky=True + ) + + if len(self.order_sales_match_line) == 0 and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and not self.env.user.is_leader: + self.env.user.notify_danger( + title='WARNING!!!', + message='Tidak ada matches SO', + sticky=True + ) + if not self.from_apo: if not self.matches_so and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and not self.env.user.is_leader: raise UserError("Tidak ada link dengan SO, harus approval Merchandise") diff --git a/indoteknik_custom/models/shipment_group.py b/indoteknik_custom/models/shipment_group.py index 48a3fa21..87d222a6 100644 --- a/indoteknik_custom/models/shipment_group.py +++ b/indoteknik_custom/models/shipment_group.py @@ -17,6 +17,14 @@ class ShipmentGroup(models.Model): carrier_id = fields.Many2one('delivery.carrier', string='Ekspedisi') total_colly_line = fields.Float(string='Total Colly', compute='_compute_total_colly_line') + def sync_api_shipping(self): + for rec in self.shipment_line: + if rec.shipment_id.carrier_id == 173: + rec.picking_id.action_get_kgx_pod() + + if rec.shipment_id.carrier_id == 151: + rec.picking_id.track_envio_shipment() + @api.depends('shipment_line.total_colly') def _compute_total_colly_line(self): for rec in self: diff --git a/indoteknik_custom/models/vendor_approval.py b/indoteknik_custom/models/vendor_approval.py index 01d2e6a2..62fd5368 100644 --- a/indoteknik_custom/models/vendor_approval.py +++ b/indoteknik_custom/models/vendor_approval.py @@ -45,7 +45,7 @@ class VendorApproval(models.Model): def action_approve(self): for rec in self: self.check_state_so() - if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + if not self.env.user.has_group('indoteknik_custom.group_role_purchasing'): raise UserError('Hanya Merchandiser yang bisa approve') # Set state menjadi 'done' @@ -65,8 +65,8 @@ class VendorApproval(models.Model): def action_reject(self): for rec in self: self.check_state_so() - if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): - raise UserError('Hanya Merchandiser yang bisa cancel') + if not self.env.user.has_group('indoteknik_custom.group_role_purchasing'): + raise UserError('Hanya Procurement yang bisa cancel') rec.state = 'cancel' -- cgit v1.2.3