pos_loyalty_subscription/models/pos_order.py

137 lines
6.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare
from datetime import datetime, time
import pytz
class PosOrder(models.Model):
_inherit = 'pos.order'
def validate_coupon_programs(self, point_changes, new_codes):
point_changes_int = {int(k): v for k, v in point_changes.items()}
subscription_cards = self.env['loyalty.card'].browse(point_changes_int.keys()).exists().filtered(
lambda c: c.program_id.program_type == 'subscription'
)
# Validate dates and limits for subscription cards
for card in subscription_cards:
today = fields.Date.today()
if card.subscription_start_date and today < card.subscription_start_date:
return {
'successful': False,
'payload': {
'message': _('The subscription for %s is not active yet (Starts on %s).', card.partner_id.name, card.subscription_start_date),
}
}
if card.subscription_end_date and today > card.subscription_end_date:
return {
'successful': False,
'payload': {
'message': _('The subscription for %s has expired (Expired on %s).', card.partner_id.name, card.subscription_end_date),
}
}
# Check usage limit today in user local timezone
user_tz = pytz.timezone(self.env.user.tz or 'UTC')
now_utc = fields.Datetime.now()
now_local = pytz.utc.localize(now_utc).astimezone(user_tz)
midnight_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0)
start_date = midnight_local.astimezone(pytz.utc).replace(tzinfo=None)
history_count = self.env['loyalty.history'].sudo().search_count([
('card_id', '=', card.id),
('create_date', '>=', start_date),
('used', '>', 0),
])
if history_count >= 1:
return {
'successful': False,
'payload': {
'message': _('Customer %s has already claimed their subscription free product today.', card.partner_id.name),
}
}
# Bypass the points validation by temporarily mocking subscription cards points to a high value.
original_points = {card: card.points for card in subscription_cards}
for card in subscription_cards:
card.points = 9999.0
try:
res = super().validate_coupon_programs(point_changes, new_codes)
finally:
for card, points in original_points.items():
card.points = points
return res
def _check_existing_loyalty_cards(self, coupon_data):
"""Extend core to also match existing subscription cards so they are
reused (point-updated) rather than triggering new-card creation."""
# Run the standard matching first (covers 'loyalty' and 'ewallet').
super()._check_existing_loyalty_cards(coupon_data)
# Now handle 'subscription' type programs the same way.
coupon_key_to_modify = []
for coupon_id, coupon_vals in coupon_data.items():
partner_id = coupon_vals.get('partner_id', False)
if not partner_id:
continue
program = self.env['loyalty.program'].browse(coupon_vals['program_id']).exists()
if not program or program.program_type != 'subscription':
continue
existing = self.env['loyalty.card'].search([
('partner_id', '=', partner_id),
('program_id', '=', coupon_vals['program_id']),
], limit=1)
if existing:
coupon_vals['coupon_id'] = existing.id
coupon_key_to_modify.append([coupon_id, existing.id])
for old_key, new_key in coupon_key_to_modify:
coupon_data[new_key] = coupon_data.pop(old_key)
def confirm_coupon_programs(self, coupon_data):
"""Strip any to-be-created (negative-id) coupon_data entries that
target a subscription program before handing off to core.
Background: the POS JS adds a {points: 0} couponPointChange for every
is_nominative program (applies_on='both') whenever a partner is on the
order including Subscription Direksi. Core would then create a fresh
loyalty.card for that partner. Subscription cards must only be created
manually, so we intercept and remove these phantom entries here.
"""
coupon_data_int = {int(k): v for k, v in coupon_data.items()}
# Collect negative (new-card) ids that belong to subscription programs.
keys_to_remove = []
for coupon_id, coupon_vals in coupon_data_int.items():
if coupon_id >= 0:
# Positive id = existing card; let core handle it normally.
continue
program = self.env['loyalty.program'].browse(
coupon_vals.get('program_id')
).exists()
if program and program.program_type == 'subscription':
keys_to_remove.append(coupon_id)
# Remove the ghost entries (work on the original string-key dict).
for key in keys_to_remove:
coupon_data.pop(key, None)
coupon_data.pop(str(key), None)
# Run super to process normal workflow
res = super().confirm_coupon_programs(coupon_data)
# After points calculations/deductions are processed, reset the points
# back to 0.0 for any subscription cards that were actually applied.
coupon_data_int = {int(k): v for k, v in coupon_data.items()}
for card_id in coupon_data_int.keys():
card = self.env['loyalty.card'].browse(card_id).exists()
if card and card.program_id.program_type == 'subscription':
card.points = 0.0
return res