summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRafi Zadanly <zadanlyr@gmail.com>2023-09-11 13:18:02 +0700
committerRafi Zadanly <zadanlyr@gmail.com>2023-09-11 13:18:02 +0700
commit1694e042acb29b476815d29f54f2ec95d37883be (patch)
treed502c76d333cc4c0602e01a98eeb7fb7f0461c01
parent02c2304b242245250177fec6ab3c911d9acba781 (diff)
parent9a630354c1d00e218595db9b2e8cd0b7df9ed97c (diff)
Merge branch 'feature/voucher-group' into production
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py22
-rw-r--r--indoteknik_api/controllers/api_v1/stock_picking.py35
-rw-r--r--indoteknik_api/controllers/api_v1/voucher.py84
-rwxr-xr-xindoteknik_custom/models/__init__.py1
-rwxr-xr-xindoteknik_custom/models/sale_order.py75
-rw-r--r--indoteknik_custom/models/sale_order_line.py1
-rw-r--r--indoteknik_custom/models/stock_picking.py15
-rw-r--r--indoteknik_custom/models/voucher.py182
-rw-r--r--indoteknik_custom/models/voucher_line.py20
-rw-r--r--indoteknik_custom/models/website_user_cart.py12
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv1
-rwxr-xr-xindoteknik_custom/views/sale_order.xml21
-rwxr-xr-xindoteknik_custom/views/voucher.xml33
13 files changed, 390 insertions, 112 deletions
diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py
index 4f6393a6..adc89f66 100644
--- a/indoteknik_api/controllers/api_v1/sale_order.py
+++ b/indoteknik_api/controllers/api_v1/sale_order.py
@@ -343,28 +343,8 @@ class SaleOrder(controller.Controller):
voucher_code = params['value']['voucher']
voucher = request.env['voucher'].search([('code', '=', voucher_code)])
if voucher:
- amount_untaxed = 0
- manufacture_ids = [x.id for x in voucher.manufacture_ids]
- for line in sale_order.order_line:
- manufacture_id = line.product_id.x_manufacture.id or False
- if len(manufacture_ids) == 0 or manufacture_id in manufacture_ids:
- amount_untaxed += line.price_subtotal
-
- voucher_discount = voucher.calculate_discount(amount_untaxed)
sale_order.voucher_id = voucher.id
- sale_order.amount_voucher_disc = voucher_discount
-
- for line in sale_order.order_line:
- manufacture_id = line.product_id.x_manufacture.id or False
- if len(manufacture_ids) > 0 and manufacture_id not in manufacture_ids:
- continue
- voucher_disc_line = line.price_subtotal / amount_untaxed * voucher_discount
- line.amount_voucher_disc = voucher_disc_line
-
- voucher_disc_item = voucher_disc_line / line.product_uom_qty
- voucher_disc_unit = line.price_unit - voucher_disc_item
-
- line.discount += (line.price_unit - voucher_disc_unit) / line.price_unit * 100
+ sale_order.apply_voucher()
cart_ids = [x['cart_id'] for x in products]
user_cart.browse(cart_ids).unlink()
diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py
index e0a60c98..74f4564a 100644
--- a/indoteknik_api/controllers/api_v1/stock_picking.py
+++ b/indoteknik_api/controllers/api_v1/stock_picking.py
@@ -13,6 +13,7 @@ class StockPicking(controller.Controller):
get_params = self.get_request_params(kw, {
'partner_id': ['number'],
'q': [],
+ 'status': [],
'limit': ['default:0', 'number'],
'offset': ['default:0', 'number']
})
@@ -27,18 +28,35 @@ class StockPicking(controller.Controller):
child_ids = request.env['res.partner'].browse(partner_id).get_child_ids()
+ pending_domain = [('driver_departure_date', '=', False), ('driver_arrival_date', '=', False)]
+ shipment_domain = [('driver_departure_date', '!=', False), ('driver_arrival_date', '=', False)]
+ completed_domain = [('driver_departure_date', '!=', False), ('driver_arrival_date', '!=', False)]
+
picking_model = request.env['stock.picking']
- default_domain = [('partner_id', 'in', child_ids), ('sale_id', '!=', False), ('origin', 'ilike', 'SO%'), ('state', '!=', 'cancel')]
+ default_domain = [
+ ('partner_id', 'in', child_ids),
+ ('sale_id', '!=', False),
+ ('origin', 'ilike', 'SO%'),
+ ('state', '!=', 'cancel')
+ ]
- domain = default_domain
+ domain = default_domain.copy()
if params['q']:
query_like = '%' + params['q'].replace(' ', '%') + '%'
domain += ['|', '|', ('name', 'ilike', query_like), ('sale_id.client_order_ref', 'ilike', query_like), ('delivery_tracking_no', 'ilike', query_like)]
+
+ if params['status'] == 'pending':
+ domain += pending_domain
+ elif params['status'] == 'shipment':
+ domain += shipment_domain
+ elif params['status'] == 'completed':
+ domain += completed_domain
stock_pickings = picking_model.search(domain, offset=offset, limit=limit, order='create_date desc')
res_pickings = []
for picking in stock_pickings:
manifests = picking.get_manifests()
+
res_pickings.append({
'id': picking.id,
'name': picking.name,
@@ -50,21 +68,18 @@ class StockPicking(controller.Controller):
'client_order_ref': picking.sale_id.client_order_ref or ''
},
'delivered': picking.waybill_id.delivered or picking.driver_arrival_date != False,
+ 'status': picking.shipping_status,
'carrier_name': picking.carrier_id.name or '',
'last_manifest': next(iter(manifests), None)
})
- pending_count = picking_model.search_count(default_domain + [('driver_departure_date', '=', False), ('driver_arrival_date', '=', False)])
- shipment_count = picking_model.search_count(default_domain + [('driver_departure_date', '!=', False), ('driver_arrival_date', '=', False)])
- completed_count = picking_model.search_count(default_domain + [('driver_departure_date', '!=', False), ('driver_arrival_date', '!=', False)])
-
return self.response({
'summary': {
- 'pending_count': pending_count,
- 'shipment_count': shipment_count,
- 'completed_count': completed_count
+ 'pending_count': picking_model.search_count(default_domain + pending_domain),
+ 'shipment_count': picking_model.search_count(default_domain + shipment_domain),
+ 'completed_count': picking_model.search_count(default_domain + completed_domain)
},
- 'picking_total': picking_model.search_count(default_domain),
+ 'picking_total': picking_model.search_count(domain),
'pickings': res_pickings
})
diff --git a/indoteknik_api/controllers/api_v1/voucher.py b/indoteknik_api/controllers/api_v1/voucher.py
index 0b769ee7..dfe9ceba 100644
--- a/indoteknik_api/controllers/api_v1/voucher.py
+++ b/indoteknik_api/controllers/api_v1/voucher.py
@@ -1,6 +1,7 @@
from .. import controller
from odoo import http
from odoo.http import request
+from bs4 import BeautifulSoup
class Voucher(controller.Controller):
@@ -11,7 +12,7 @@ class Voucher(controller.Controller):
def get_vouchers(self, **kw):
cart = request.env['website.user.cart']
code = kw.get('code')
- user_id = kw.get('user_id')
+ user_id = int(kw.get('user_id', 0))
source = kw.get('source')
visibility = ['public']
@@ -25,57 +26,52 @@ class Voucher(controller.Controller):
parameter += [('visibility', 'in', visibility)]
vouchers = request.env['voucher'].get_active_voucher(parameter)
- vouchers = vouchers.res_format()
checkout = cart.get_user_checkout(user_id, source=source)
+ products = checkout['products']
- for voucher in vouchers:
- apply_status = ''
- products = checkout['products']
- min_purchase_amount = voucher['min_purchase_amount']
- can_apply = False
- difference_to_apply = 0
-
- manufacture_ids = voucher['manufacture_ids']
- subtotal = 0
- has_match_manufacture = False
- for product in products:
- price = product['price']['price']
- price_discount = product['price']['price_discount']
- quantity = product['quantity']
- manufacture_id = product['manufacture']['id'] or False
+ user = request.env['res.users'].search([('id', '=', user_id)], limit=1)
+ if not user:
+ return self.response([])
- if len(manufacture_ids) == 0 or manufacture_id in manufacture_ids:
- has_match_manufacture = True
+ order_line = []
+ for product in products:
+ order_line.append({
+ 'product_id': request.env['product.product'].browse(product['id']),
+ 'price': product['price']['price'],
+ 'discount': product['price']['discount_percentage'],
+ 'qty': product['quantity'],
+ 'subtotal': product['subtotal']
+ })
+
+ results = []
+ for voucher in vouchers:
+ if voucher.limit > 0 and voucher.count_order >= voucher.limit:
+ continue
- if product['has_flashsale']:
- continue
+ partner_voucher_orders = []
+ for order in voucher.order_ids:
+ if order.partner_id.id == user.partner_id.id:
+ partner_voucher_orders.append(order)
+
+ if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user:
+ continue
+
+ voucher_info = voucher.apply(order_line)
+ voucher_discount = voucher_info['discount']['all']
- purchase_amt = price * quantity
- discount_amt = (price - price_discount) * quantity
- subtotal += purchase_amt - discount_amt
+ valid_order = voucher_info['valid_order']
- has_flashsale_products = any(product['has_flashsale'] for product in products)
- if not has_match_manufacture:
- apply_status = 'UM' # Unqualified Manufacture
- elif subtotal < min_purchase_amount:
- apply_status = 'MPA' # Minimum Purchase Amount
- if has_flashsale_products:
- apply_status += '-HF' # Has Flashsale
- else:
- can_apply = True
+ can_apply = True if valid_order and voucher_discount > 0 else False
- if subtotal < min_purchase_amount:
- difference_to_apply = min_purchase_amount - subtotal
+ voucher_res = voucher.format()
+ voucher_res['can_apply'] = can_apply
+ voucher_res['discount_voucher'] = voucher_discount
- obj_voucher = request.env['voucher'].browse(voucher['id'])
- discount_voucher = obj_voucher.calculate_discount(subtotal)
+ cleaned_tnc = BeautifulSoup(voucher.terms_conditions or '', "html.parser").get_text()
+ voucher_res['terms_conditions'] = voucher.terms_conditions if cleaned_tnc else voucher.generate_tnc()
- voucher['can_apply'] = can_apply
- voucher['apply_status'] = apply_status
- voucher['has_flashsale_products'] = has_flashsale_products
- voucher['discount_voucher'] = discount_voucher
- voucher['difference_to_apply'] = difference_to_apply
+ results.append(voucher_res)
- sorted_vouchers = sorted(vouchers, key=lambda x: x['can_apply'], reverse=True)
+ sorted_results = sorted(results, key=lambda x: x['can_apply'], reverse=True)
- return self.response(sorted_vouchers)
+ return self.response(sorted_results)
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py
index b8be14ba..ee5a3a87 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -73,6 +73,7 @@ from . import airway_bill
from . import airway_bill_manifest
from . import account_move_due_extension
from . import voucher
+from . import voucher_line
from . import bill_receipt
from . import account_move_multi_update
from . import account_financial_report
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 65132fda..8317e1fd 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -73,7 +73,8 @@ class SaleOrder(models.Model):
sppkp = fields.Char(string="SPPKP")
npwp = fields.Char(string="NPWP")
purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total')
- voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher')
+ voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False)
+ applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False)
amount_voucher_disc = fields.Float(string='Voucher Discount')
source_id = fields.Many2one('utm.source', 'Source', domain="[('id', 'in', [32, 59, 60, 61])]")
estimated_arrival_days = fields.Integer('Estimated Arrival Days', default=0)
@@ -377,3 +378,75 @@ class SaleOrder(models.Model):
else:
order.grand_total = order.amount_total
+ def action_apply_voucher(self):
+ for order in self.order_line:
+ if order.program_line_id:
+ raise UserError('Voucher tidak dapat digabung dengan promotion program')
+
+ 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 apply_voucher(self):
+ order_line = []
+ for line in self.order_line:
+ order_line.append({
+ 'product_id': line.product_id,
+ 'price': line.price_unit,
+ 'discount': line.discount,
+ 'qty': line.product_uom_qty,
+ 'subtotal': line.price_subtotal
+ })
+ voucher = self.voucher_id.apply(order_line)
+
+ 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]
+
+ manufacture_id = line.product_id.x_manufacture.id
+ if voucher_type == 'brand':
+ used_total = used_total.get(manufacture_id)
+ used_discount = used_discount.get(manufacture_id)
+
+ if not used_total or not used_discount:
+ continue
+
+ line_contribution = line.price_subtotal / used_total
+ line_voucher = used_discount * line_contribution
+ line_voucher_item = line_voucher / line.product_uom_qty
+ 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
+
+ self.amount_voucher_disc = voucher['discount']['all']
+ self.applied_voucher_id = self.voucher_id
+
+ def cancel_voucher(self):
+ self.applied_voucher_id = False
+ self.amount_voucher_disc = 0
+ for line in self.order_line:
+ line.amount_voucher_disc = 0
+ line.discount = line.initial_discount
+ line.initial_discount = False
+
+
+
+
diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py
index 4e1209cb..4ec48b55 100644
--- a/indoteknik_custom/models/sale_order_line.py
+++ b/indoteknik_custom/models/sale_order_line.py
@@ -6,6 +6,7 @@ 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_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(
'res.partner', string='Vendor', readonly=True,
change_default=True, index=True, tracking=1,
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 0e6137eb..19ac25d0 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -73,8 +73,22 @@ class StockPicking(models.Model):
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", readonly=True)
+<<<<<<< HEAD
carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method')
+=======
+ shipping_status = fields.Char(string='Shipping Status', compute="_compute_shipping_status")
+
+ def _compute_shipping_status(self):
+ for rec in self:
+ status = 'pending'
+ if rec.driver_departure_date and not rec.driver_arrival_date:
+ status = 'shipment'
+ elif rec.driver_departure_date and rec.driver_arrival_date:
+ status = 'completed'
+
+ rec.shipping_status = status
+>>>>>>> feature/voucher-group
def action_create_invoice_from_mr(self):
"""Create the invoice associated to the PO.
@@ -397,6 +411,7 @@ class StockPicking(models.Model):
'receiver_city': ''
},
'delivered': False,
+ 'status': self.shipping_status,
'waybill_number': self.delivery_tracking_no or '',
'delivery_status': None,
'eta': self.generate_eta_delivery(),
diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py
index a7151398..4865db2f 100644
--- a/indoteknik_custom/models/voucher.py
+++ b/indoteknik_custom/models/voucher.py
@@ -1,6 +1,7 @@
from odoo import models, fields, api
from datetime import datetime, timedelta
from odoo.exceptions import ValidationError
+import locale
class Voucher(models.Model):
@@ -13,22 +14,20 @@ class Voucher(models.Model):
code = fields.Char(string='Code', help='Kode voucher yang akan berlaku untuk pengguna')
description = fields.Text(string='Description')
discount_amount = fields.Integer(string='Discount Amount')
- discount_type = fields.Selection(
+ discount_type = fields.Selection(string='Discount Type',
selection=[
('percentage', 'Percentage'),
('fixed_price', 'Fixed Price'),
- ],
- string='Discount Type',
+ ],
help='Select the type of discount:\n'
- '- Percentage: Persentage dari total harga.\n'
+ '- Percentage: Persentase dari total harga.\n'
'- Fixed Price: Jumlah tetap yang dikurangi dari harga total.'
)
- visibility = fields.Selection(
+ visibility = fields.Selection(string='Visibility',
selection=[
('public', 'Public'),
('private', 'Private')
],
- string='Visibility',
help='Select the visibility:\n'
'- Public: Ditampilkan kepada seluruh pengguna.\n'
'- Private: Tidak ditampilkan kepada seluruh pengguna.'
@@ -37,43 +36,60 @@ class Voucher(models.Model):
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')
max_discount_amount = fields.Integer(string='Max. Discount Amount', help='Max nominal terhadap persentase diskon')
- order_ids = fields.One2many('sale.order', 'voucher_id', string='Order')
- limit = fields.Integer(string='Limit', help='Voucher limit by sale order. Masukan 0 untuk tanpa limit')
+ order_ids = fields.One2many('sale.order', 'applied_voucher_id', string='Order')
+ limit = fields.Integer(
+ string='Limit',
+ default=0,
+ help='Batas penggunaan voucher keseluruhan. Isi dengan angka 0 untuk penggunaan tanpa batas'
+ )
+ limit_user = fields.Integer(
+ 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')
+ 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=[
+ ('all', "All product"),
+ ('brand', "Selected product brand"),
+ ])
+ count_order = fields.Integer(string='Count Order', compute='_compute_count_order')
@api.constrains('description')
def _check_description_length(self):
for record in self:
if record.description and len(record.description) > 120:
- raise ValidationError('Description cannot exceed 120 characters')
+ raise ValidationError('Deskripsi tidak boleh lebih dari 120 karakter')
+
+ @api.constrains('limit', 'limit_user')
+ def _check_limit(self):
+ for rec in self:
+ if rec.limit_user > rec.limit:
+ raise ValidationError('Limit user tidak boleh lebih besar dari limit keseluruhan')
def _compute_display_name(self):
for voucher in self:
voucher.display_name = f'{voucher.name} ({voucher.code})'
+ def _compute_count_order(self):
+ for rec in self:
+ rec.count_order = len([x for x in rec.order_ids if x.state != 'cancel'])
+
def res_format(self):
datas = [voucher.format() for voucher in self]
return datas
def format(self):
ir_attachment = self.env['ir.attachment']
- discount_type = self.discount_type
- max_discount_amount = self.max_discount_amount if discount_type == 'percentage' else 0
data = {
'id': self.id,
'image': ir_attachment.api_image('voucher', 'image', self.id),
'name': self.name,
'code': self.code,
'description': self.description,
- 'discount_amount': self.discount_amount,
- 'discount_type': discount_type,
'remaining_time': self._res_remaining_time(),
- 'min_purchase_amount': self.min_purchase_amount,
- 'max_discount_amount': max_discount_amount,
- 'manufacture_names': ", ".join([x.x_name for x in self.manufacture_ids]),
- 'manufacture_ids': [x.id for x in self.manufacture_ids],
- 'excl_pricelist_ids': [x.id for x in self.excl_pricelist_ids],
}
return data
@@ -97,6 +113,82 @@ class Voucher(models.Model):
calculate_time = self.end_time - datetime.now()
return round(calculate_time.total_seconds())
+ def filter_order_line(self, order_line):
+ voucher_manufacture_ids = self.collect_manufacture_ids()
+ results = []
+ 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:
+ continue
+
+ 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': {} }
+ 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': {} }
+
+ if self.apply_type == 'all':
+ 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
+ else:
+ result['all'] = self.discount_amount
+
+ return result
+
+ for line in self.voucher_line:
+ manufacture_id = line.manufacture_id.id
+ total_brand = total['brand'].get(manufacture_id, 0)
+
+ discount_brand = 0
+ if total_brand < line.min_purchase_amount:
+ discount_brand = 0
+ 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
+ else:
+ discount_brand = line.discount_amount
+
+ 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)
+ discount = self.calc_discount_amount(amount_total)
+ return {
+ 'discount': discount,
+ 'total': amount_total,
+ 'type': self.apply_type,
+ 'valid_order': 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
@@ -111,11 +203,57 @@ class Voucher(models.Model):
return 0
- def get_active_voucher(self, parameter):
+ def get_active_voucher(self, domain):
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- parameter += [
+ domain += [
('start_time', '<=', current_time),
('end_time', '>=', current_time),
]
- vouchers = self.search(parameter, order='min_purchase_amount ASC')
- return vouchers \ No newline at end of file
+ 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(f'<li>Voucher berlaku {self._res_remaining_time()} lagi</li>')
+ if len(self.voucher_line) > 0:
+ brand_names = ', '.join([x.manufacture_id.x_name for x in self.voucher_line])
+ tnc.append(f'<li>Voucher berlaku untuk produk dari brand {brand_names}</li>')
+ tnc.append(self.generate_detail_tnc())
+ tnc.append('</ol>')
+
+ return ' '.join(tnc)
+
+ def generate_detail_tnc(self):
+ def format_currency(amount):
+ locale.setlocale(locale.LC_ALL, 'id_ID')
+ formatted_amount = locale.format("%d", amount, grouping=True)
+ return f'Rp{formatted_amount}'
+
+ 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))
+
+ 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('</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))
+
+ 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'<li>{line_tnc}</li>')
+ return ' '.join(tnc)
+
diff --git a/indoteknik_custom/models/voucher_line.py b/indoteknik_custom/models/voucher_line.py
new file mode 100644
index 00000000..8b449d1f
--- /dev/null
+++ b/indoteknik_custom/models/voucher_line.py
@@ -0,0 +1,20 @@
+from odoo import models, fields
+
+
+class Voucher(models.Model):
+ _name = 'voucher.line'
+
+ voucher_id = fields.Many2one('voucher', string='Voucher')
+ manufacture_id = fields.Many2one('x_manufactures', string='Brand')
+ min_purchase_amount = fields.Integer(string='Min. Purchase Amount', help='Nominal minimum untuk dapat menggunakan voucher. Isi 0 jika tidak ada minimum purchase amount')
+ discount_amount = fields.Integer(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.'
+ )
+ max_discount_amount = fields.Integer(string='Max. Discount Amount', help='Max nominal terhadap persentase diskon') \ No newline at end of file
diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py
index bf7016f8..b3695ba1 100644
--- a/indoteknik_custom/models/website_user_cart.py
+++ b/indoteknik_custom/models/website_user_cart.py
@@ -71,7 +71,17 @@ class WebsiteUserCart(models.Model):
subtotal = total_purchase - total_discount
discount_voucher = 0
if voucher:
- discount_voucher = voucher.calculate_discount(subtotal)
+ order_line = []
+ for product in products:
+ order_line.append({
+ 'product_id': self.env['product.product'].browse(product['id']),
+ 'price': product['price']['price'],
+ 'discount': product['price']['discount_percentage'],
+ 'qty': product['quantity'],
+ 'subtotal': product['subtotal']
+ })
+ voucher_info = voucher.apply(order_line)
+ discount_voucher = voucher_info['discount']['all']
subtotal -= discount_voucher
tax = round(subtotal * 0.11)
grand_total = subtotal + tax
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index e1480dd3..0e4fda51 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -61,6 +61,7 @@ access_requisition_purchase_match,access.requisition.purchase.match,model_requis
access_token_storage,access.token_storage,model_token_storage,,1,1,1,1
access_product_sla,access.product_sla,model_product_sla,,1,1,1,1
access_voucher,access.voucher,model_voucher,,1,1,1,1
+access_voucher_line,access.voucher.line,model_voucher_line,,1,1,1,1
access_bill_receipt,access.bill.receipt,model_bill_receipt,,1,1,1,1
access_bill_receipt_line,access.bill.receipt.line,model_bill_receipt_line,,1,1,1,1
access_account_move_multi_update,access.account.move.multi_update,model_account_move_multi_update,,1,1,1,1
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 91ce1463..f2cab699 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -26,7 +26,17 @@
<field name="delivery_amt"/>
<field name="fee_third_party"/>
<field name="total_percent_margin"/>
- <field name="voucher_id" readonly="1" />
+ <label for="voucher_id"/>
+ <div class="o_row">
+ <field name="voucher_id" id="voucher_id" attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"/>
+ <field name="applied_voucher_id" invisible="1" />
+ <button name="action_apply_voucher" type="object" string="Apply" confirm="Anda yakin untuk menggunakan voucher?" help="Apply the selected voucher" class="btn-link mb-1 px-0" icon="fa-plus"
+ attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"
+ />
+ <button name="cancel_voucher" type="object" string="Cancel" confirm="Anda yakin untuk membatalkan penggunaan voucher?" help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
+ attrs="{'invisible': ['|', ('applied_voucher_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
+ />
+ </div>
</field>
<field name="source_id" position="attributes">
<attribute name="invisible">1</attribute>
@@ -49,6 +59,11 @@
<field name="date_doc_kirim" readonly="1"/>
<field name="notification" readonly="1"/>
</field>
+ <xpath expr="//form/sheet/notebook/page/field[@name='order_line']" position="attributes">
+ <attribute name="attrs">
+ {'readonly': ['|', ('applied_voucher_id', '!=', False), ('state', 'in', ('done','cancel'))]}
+ </attribute>
+ </xpath>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']" position="attributes">
<attribute name="attrs">
{
@@ -75,7 +90,7 @@
<field name="item_percent_margin"/>
<field name="note_procurement" optional="hide"/>
<field name="vendor_subtotal" optional="hide"/>
- <field name="amount_voucher_disc" string="Voucher" optional="hide"/>
+ <field name="amount_voucher_disc" string="Voucher" readonly="1" optional="hide"/>
<field name="program_line_id" optional="1" />
</xpath>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']" position="before">
@@ -85,7 +100,7 @@
<field name="grand_total"/>
<label for="amount_voucher_disc" string="Voucher" />
<div>
- <field class="mb-0" name="amount_voucher_disc" string="Voucher"/>
+ <field class="mb-0" name="amount_voucher_disc" string="Voucher" readonly="1"/>
<div class="text-right mb-2"><small>*Hanya informasi</small></div>
</div>
<field name="total_margin"/>
diff --git a/indoteknik_custom/views/voucher.xml b/indoteknik_custom/views/voucher.xml
index 7b181c62..97634d7d 100755
--- a/indoteknik_custom/views/voucher.xml
+++ b/indoteknik_custom/views/voucher.xml
@@ -28,8 +28,16 @@
<group>
<field name="image" widget="image" width="120"/>
<field name="name" required="1" />
+ <field name="code" required="1" />
+ <field name="visibility" required="1" />
+ <field name="start_time" required="1"/>
+ <field name="end_time" required="1"/>
+ <field name="limit" required="1"/>
+ <field name="limit_user" required="1"/>
+ <field name="apply_type" required="1" />
+ <field name="excl_pricelist_ids" widget="many2many_tags" domain="[('id', 'in', [4, 15037, 15038, 15039])]"/>
</group>
- <group string="Discount Settings">
+ <group string="Discount Settings" attrs="{'invisible': [('apply_type', '!=', 'all')]}">
<field name="min_purchase_amount" widget="monetary" required="1" />
<field name="discount_type" required="1" />
@@ -52,21 +60,26 @@
<field name="max_discount_amount" widget="monetary" required="1" attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"/>
</group>
- <group string="Rules">
- <field name="code" required="1" />
- <field name="visibility" required="1" />
- <field name="start_time" required="1"/>
- <field name="end_time" required="1"/>
- <field name="limit" required="1"/>
- <field name="manufacture_ids" widget="many2many_tags"/>
- <field name="excl_pricelist_ids" widget="many2many_tags" domain="[('id', 'in', [4, 15037, 15038, 15039])]"/>
- </group>
</group>
<notebook>
+ <page name="voucher_line" string="Voucher Line" attrs="{'invisible': [('apply_type', '!=', 'brand')]}">
+ <field name="voucher_line">
+ <tree editable="bottom">
+ <field name="manufacture_id" required="1" />
+ <field name="min_purchase_amount" required="1" />
+ <field name="discount_type" required="1" />
+ <field name="discount_amount" required="1" />
+ <field name="max_discount_amount" required="1" attrs="{'readonly': [('discount_type', '!=', 'percentage')]}" />
+ </tree>
+ </field>
+ </page>
<page name="description" string="Description">
<label for="description" string="Max 120 characters:" class="font-weight-normal mb-2 oe_edit_only"/>
<field name="description" placeholder="Insert short description..." />
</page>
+ <page name="terms_conditions" string="Terms and Conditions">
+ <field name="terms_conditions" />
+ </page>
<page name="order_page" string="Orders">
<field name="order_ids" readonly="1" />
</page>