diff options
Diffstat (limited to 'indoteknik_custom/models/voucher.py')
| -rw-r--r-- | indoteknik_custom/models/voucher.py | 207 |
1 files changed, 142 insertions, 65 deletions
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('<ol>') - tnc.append('<li>Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher</li>') + tnc.append( + '<li>Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher</li>') tnc.append(f'<li>Voucher berlaku {self._res_remaining_time()} lagi</li>') tnc.append(f'<li>Voucher tidak bisa digunakan apabila terdapat produk flash sale</li>') + if self.voucher_category: + category_names = ', '.join([cat.name for cat in self.voucher_category]) + tnc.append( + f'<li>Voucher hanya berlaku untuk produk dalam kategori {category_names} dan sub-kategorinya</li>') + tnc.append( + f'<li>Voucher tidak dapat digunakan jika ada produk di keranjang yang tidak termasuk dalam kategori tersebut</li>') + if len(self.voucher_line) > 0: - brand_names = ', '.join([x.manufacture_id.x_name or '' for x in self.voucher_line]) - tnc.append(f'<li>Voucher berlaku untuk produk dari brand {brand_names}</li>') + tnc.append(f'<li>Voucher berlaku untuk produk dari brand terpilih</li>') tnc.append(self.generate_detail_tnc()) tnc.append('</ol>') - + 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('<li>') 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('</li>') 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'<li>{line_tnc}</li>') 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) |
