Discount::generateCode()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * Discount.php
4
 *
5
 * @author Bram de Leeuw
6
 * Date: 30/03/17
7
 */
8
9
namespace Broarm\EventTickets;
10
11
use CalendarEvent;
12
use DateField;
13
use DropdownField;
14
use Group;
15
use ManyManyList;
16
use Member;
17
use NumericField;
18
use SS_Datetime;
19
use TagField;
20
use TextareaField;
21
use TextField;
22
23
/**
24
 * Class Discount
25
 *
26
 * @property string Code
27
 * @property string ValidFrom
28
 * @property string ValidTill
29
 * @property float  Amount
30
 * @property int    Uses
31
 * @property bool   AppliesTo
32
 * @property string DiscountType
33
 * @method ManyManyList Groups()
34
 * @method ManyManyList Events()
35
 * @method ManyManyList Reservations()
36
 */
37
class Discount extends PriceModifier
38
{
39
    const PRICE = 'PRICE';
40
    const PERCENTAGE = 'PERCENTAGE';
41
    const APPLIES_EACH_TICKET = 'EACH_TICKET';
42
43
    private static $singular_name = 'Discount';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
44
45
    private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
46
        'Description' => 'Text',
47
        'Amount' => 'Decimal',
48
        'Uses' => 'Int',
49
        'DiscountType' => 'Enum("PRICE,PERCENTAGE","PRICE")',
50
        'Code' => 'Varchar(255)',
51
        'ValidFrom' => 'SS_Datetime',
52
        'ValidTill' => 'SS_Datetime',
53
        'AppliesTo' => 'Enum("CART,EACH_TICKET","CART")'
54
    );
55
56
    private static $default_sort = "ValidFrom DESC";
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
57
58
    private static $many_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
59
        'Groups' => 'Group',
60
        'Events' => 'CalendarEvent'
61
    );
62
63
    private static $indexes = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
64
        'Code' => 'unique("Code")'
65
    );
66
67
    private static $summary_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
68
        'Code' => 'Code',
69
        'Description' => 'Description',
70
        'ValidFrom.Nice' => 'Valid from',
71
        'ValidTill.Nice' => 'Valid till',
72
        'Reservations.Count' => 'Uses'
73
    );
74
75
    private static $defaults = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
76
        'Uses' => 1
77
    );
78
79
    /**
80
     * Create the needed cms fields
81
     *
82
     * @return \FieldList
83
     */
84
    public function getCMSFields()
85
    {
86
        $fields = parent::getCMSFields();
87
88
        $types = $this->dbObject('DiscountType')->enumValues();
89
        $appliesTo = $this->dbObject('AppliesTo')->enumValues();
90
91
        $fields->addFieldsToTab('Root.Main', array(
92
            $code = TextField::create('Code', 'Code'),
93
            TextareaField::create('Description', 'Description')->setDescription('The description is only visible in the cms'),
94
            DropdownField::create('DiscountType', _t('Discount.TYPE', 'Type of discount'), $types),
95
            DropdownField::create('AppliesTo', _t('Discount.AppliesTo', 'Discount applies to'), $appliesTo),
96
            NumericField::create('Amount', _t('Discount.AMOUNT', 'Amount')),
97
            NumericField::create('Uses', _t('Discount.USES', 'Maximum number of uses')),
98
            $validFrom = DateField::create('ValidFrom', _t('Discount.VALID_FROM', 'Valid from')),
99
            $validTill = DateField::create('ValidTill', _t('Discount.VALID_TILL', 'Valid till')),
100
            TagField::create('Groups', _t('Discount.GROUPS', 'Constrain to groups'), Group::get())
101
                ->setShouldLazyLoad(true),
102
            TagField::create('Events', _t('Discount.EVENTS', 'Constrain to events'), CalendarEvent::get())
103
                ->setShouldLazyLoad(true)
104
        ));
105
106
        $code->setDescription(
107
            _t('Discount.CODE_HELP', 'The code is generated after saving')
108
        );
109
110
        $validFrom
111
            ->setConfig('showcalendar', true)
112
            ->setDescription(_t('Discount.VALID_FROM_HELP', 'If no date is set the current date is used'));
113
        $validTill
114
            ->setConfig('showcalendar', true)
115
            ->setDescription(_t('Discount.VALID_TILL_HELP', 'If no date is set the current date + 1 year is used'));
116
117
        $fields->removeByName(array('Title'));
118
        return $fields;
119
    }
120
121
    public function onBeforeWrite()
122
    {
123
        // Generate or validate the set code
124
        if (empty($this->Code)) {
125
            $this->Code = $this->generateCode();
126
        } elseif (empty($this->Title) && $codes = self::get()->filter('Code:PartialMatch', $this->Code)) {
127
            if ($codes->count() >= 1) {
128
                $this->Code .= "-{$codes->count()}";
129
            }
130
        }
131
132
        // Set the title
133
        $this->Title = $this->Code;
134
135
        // Set the default dates
136
        if (empty($this->ValidFrom) && empty($this->ValidTill)) {
137
            $format = 'Y-m-d';
138
            $this->ValidFrom = $start = date($format);
139
            $this->ValidTill = date($format, strtotime("$start + 1 year"));
140
        }
141
142
        parent::onBeforeWrite();
143
    }
144
145
    /**
146
     * Return the table title
147
     *
148
     * @return string
149
     */
150
    public function getTableTitle()
151
    {
152
        return _t('Discount.DISCOUNT', 'Discount');
153
    }
154
155
    /**
156
     * Check if the discount exceeded the maximum uses
157
     *
158
     * @return bool
159
     */
160
    public function validateUses()
161
    {
162
        return $this->Reservations()->count() <= $this->Uses;
163
    }
164
165
    /**
166
     * Calculate the discount
167
     *
168
     * @param float $total
169
     * @param Reservation $reservation
170
     */
171
    public function updateTotal(&$total, Reservation $reservation)
172
    {
173
        switch ($this->DiscountType) {
174
            case self::PERCENTAGE:
175
                $discount = ($total / 100 * $this->Amount);
176
                $total -= $discount;
177
                break;
178
            default:
179
                // case price
180
                // A Percentage always get's calculated over all tickets
181
                $discount = $this->AppliesTo === self::APPLIES_EACH_TICKET
182
                    ? $this->Amount * $reservation->Attendees()->count()
183
                    : $this->Amount;
184
                $total -= $discount;
185
                $total = $total > 0 ? $total : 0;
186
                break;
187
        }
188
189
        // save the modification on the join
190
        $this->setPriceModification($discount);
191
    }
192
193
    /**
194
     * Check if the from and till dates are in the past and future
195
     *
196
     * @return bool
197
     */
198
    public function validateDate()
199
    {
200
        /** @var SS_Datetime $from */
201
        $from = $this->dbObject('ValidFrom');
202
        /** @var SS_Datetime $till */
203
        $till = $this->dbObject('ValidTill');
204
205
        return (bool)($from->InPast() && $till->InFuture());
206
    }
207
208
    /**
209
     * Validate the given member with the allowed groups
210
     *
211
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be null|Member?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
212
     *
213
     * @return bool
214
     */
215
    public function validateGroups(Member $member = null)
216
    {
217
        // If groups are attached to the discount, check if valid
218
        if ($this->Groups()->exists()) {
219
            if (empty($member)) {
220
                return false;
221
            } else {
222
                $validGroups = $this->Groups()->column('ID');
223
                $groupMembers = Member::get()->filter('Groups.ID:ExactMatchMulti', $validGroups);
224
                return (bool)$groupMembers->find('ID', $member->ID);
225
            }
226
        }
227
228
        return true;
229
    }
230
231
    /**
232
     * Validate if the given event is in the group of allowed events
233
     *
234
     * @param CalendarEvent $event
235
     *
236
     * @return bool
237
     */
238
    public function validateEvents(CalendarEvent $event)
239
    {
240
        // If events are attached to the discount, check if valid
241
        if ($this->Events()->exists()) {
242
            if (empty($event)) {
243
                return false;
244
            } else {
245
                $validEvents = $this->Events()->column('ID');
246
                return in_array($event->ID, $validEvents);
247
            }
248
        }
249
250
        return true;
251
    }
252
253
    /**
254
     * Generate a unique coupon code
255
     *
256
     * @return string
257
     */
258
    public function generateCode()
259
    {
260
        return uniqid($this->ID);
261
    }
262
}
263