Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
ecommerce
Commits
af9f2d4a
Commit
af9f2d4a
authored
Jul 05, 2016
by
Marko Jevtić
Committed by
GitHub
Jul 05, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #824 from edx/mjevtic/patch-update-coupon
Enable patch editing of the coupons
parents
5621f060
0356d9c4
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
199 additions
and
52 deletions
+199
-52
ecommerce/extensions/api/v2/tests/views/test_coupons.py
+35
-0
ecommerce/extensions/api/v2/views/coupons.py
+32
-13
ecommerce/static/js/models/coupon_model.js
+27
-10
ecommerce/static/js/test/specs/models/coupon_model_spec.js
+15
-0
ecommerce/static/js/test/specs/views/coupon_edit_view_spec.js
+12
-1
ecommerce/static/js/test/specs/views/form_view_spec.js
+0
-1
ecommerce/static/js/views/coupon_form_view.js
+26
-2
ecommerce/static/js/views/form_view.js
+52
-25
No files found.
ecommerce/extensions/api/v2/tests/views/test_coupons.py
View file @
af9f2d4a
...
...
@@ -80,6 +80,27 @@ class CouponViewSetTest(CouponMixin, CourseCatalogTestMixin, TestCase):
site
.
siteconfiguration
=
site_configuration
return
site
def
test_retrieve_invoice_data
(
self
):
request_data
=
{
'invoice_discount_type'
:
Invoice
.
PERCENTAGE
,
'invoice_discount_value'
:
50
,
'invoice_number'
:
'INV-00055'
,
'invoice_payment_date'
:
datetime
.
datetime
(
2016
,
1
,
1
,
tzinfo
=
pytz
.
UTC
)
.
isoformat
(),
'invoice_type'
:
Invoice
.
PREPAID
,
'tax_deducted_source'
:
None
}
invoice_data
=
CouponViewSet
()
.
retrieve_invoice_data
(
request_data
)
self
.
assertDictEqual
(
invoice_data
,
{
'discount_type'
:
request_data
[
'invoice_discount_type'
],
'discount_value'
:
request_data
[
'invoice_discount_value'
],
'number'
:
request_data
[
'invoice_number'
],
'payment_date'
:
request_data
[
'invoice_payment_date'
],
'type'
:
request_data
[
'invoice_type'
],
'tax_deducted_source'
:
request_data
[
'tax_deducted_source'
]
})
@ddt.data
(
(
Voucher
.
ONCE_PER_CUSTOMER
,
2
,
2
),
(
Voucher
.
SINGLE_USE
,
2
,
None
)
...
...
@@ -526,6 +547,20 @@ class CouponViewSetFunctionalTest(CouponMixin, CourseCatalogTestMixin, CatalogPr
baskets
=
Basket
.
objects
.
filter
(
lines__product_id
=
coupon
.
id
)
self
.
assertEqual
(
baskets
.
first
()
.
owner
.
username
,
'Test Client Username'
)
def
test_update_invoice_data
(
self
):
coupon
=
Product
.
objects
.
get
(
title
=
'Test coupon'
)
invoice
=
Invoice
.
objects
.
get
(
order__basket__lines__product
=
coupon
)
self
.
assertEqual
(
invoice
.
discount_type
,
Invoice
.
PERCENTAGE
)
CouponViewSet
()
.
update_invoice_data
(
coupon
=
coupon
,
data
=
{
'invoice_discount_type'
:
Invoice
.
FIXED
}
)
invoice
=
Invoice
.
objects
.
get
(
order__basket__lines__product
=
coupon
)
self
.
assertEqual
(
invoice
.
discount_type
,
Invoice
.
FIXED
)
@ddt.data
(
'audit'
,
'honor'
)
def
test_restricted_course_mode
(
self
,
mode
):
"""Test that an exception is raised when a black-listed course mode is used."""
...
...
ecommerce/extensions/api/v2/views/coupons.py
View file @
af9f2d4a
...
...
@@ -43,6 +43,21 @@ Voucher = get_model('voucher', 'Voucher')
CATALOG_QUERY
=
'catalog_query'
CLIENT
=
'client'
COURSE_SEAT_TYPES
=
'course_seat_types'
INVOICE_DISCOUNT_TYPE
=
'invoice_discount_type'
INVOICE_DISCOUNT_VALUE
=
'invoice_discount_value'
INVOICE_NUMBER
=
'invoice_number'
INVOICE_PAYMENT_DATE
=
'invoice_payment_date'
INVOICE_TYPE
=
'invoice_type'
TAX_DEDUCTED_SOURCE
=
'tax_deducted_source'
UPDATEABLE_INVOICE_FIELDS
=
[
INVOICE_DISCOUNT_TYPE
,
INVOICE_DISCOUNT_VALUE
,
INVOICE_NUMBER
,
INVOICE_PAYMENT_DATE
,
INVOICE_TYPE
,
TAX_DEDUCTED_SOURCE
,
]
UPDATABLE_RANGE_FIELDS
=
[
CATALOG_QUERY
,
...
...
@@ -64,14 +79,17 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
def
retrieve_invoice_data
(
self
,
request_data
):
""" Retrieve the invoice information from the request data. """
return
{
'number'
:
request_data
.
get
(
'invoice_number'
),
'type'
:
request_data
.
get
(
'invoice_type'
),
'payment_date'
:
request_data
.
get
(
'invoice_payment_date'
),
'discount_type'
:
request_data
.
get
(
'invoice_discount_type'
),
'discount_value'
:
request_data
.
get
(
'invoice_discount_value'
),
'tax_deducted_source'
:
request_data
.
get
(
'tax_deducted_source'
),
}
invoice_data
=
{}
for
field
in
UPDATEABLE_INVOICE_FIELDS
:
self
.
create_update_data_dict
(
request_data
=
request_data
,
request_data_key
=
field
,
update_dict
=
invoice_data
,
update_dict_key
=
field
.
replace
(
'invoice_'
,
''
)
)
return
invoice_data
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
"""Adds coupon to the user's basket.
...
...
@@ -373,8 +391,8 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
update_dict (dict): Dictionary containing the coupon update data
update_dict_key (str): Update data dictionary key
"""
value
=
request_data
.
get
(
request_data_key
,
''
)
if
value
:
if
request_data_key
in
request_data
:
value
=
request_data
.
get
(
request_data_key
)
update_dict
[
update_dict_key
]
=
prepare_course_seat_types
(
value
)
\
if
update_dict_key
==
COURSE_SEAT_TYPES
else
value
...
...
@@ -442,9 +460,10 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
data (dict): The request's data from which the invoice data is retrieved
and used for the updated.
"""
invoice
=
Invoice
.
objects
.
filter
(
order__basket__lines__product
=
coupon
)
update_data
=
self
.
retrieve_invoice_data
(
data
)
invoice
.
update
(
**
update_data
)
invoice_data
=
self
.
retrieve_invoice_data
(
data
)
if
invoice_data
:
Invoice
.
objects
.
filter
(
order__basket__lines__product
=
coupon
)
.
update
(
**
invoice_data
)
def
destroy
(
self
,
request
,
pk
):
# pylint: disable=unused-argument
try
:
...
...
ecommerce/static/js/models/coupon_model.js
View file @
af9f2d4a
...
...
@@ -139,6 +139,7 @@ define([
},
initialize
:
function
()
{
this
.
on
(
'change:categories'
,
this
.
updateCategory
,
this
);
this
.
on
(
'change:voucher_type'
,
this
.
changeVoucherType
,
this
);
this
.
on
(
'change:vouchers'
,
this
.
updateVoucherData
);
this
.
on
(
'change:seats'
,
this
.
updateSeatData
);
...
...
@@ -178,6 +179,12 @@ define([
return
course_id
?
course_id
.
value
:
''
;
},
updateCategory
:
function
()
{
var
categoryID
=
this
.
get
(
'categories'
)[
0
].
id
;
this
.
set
(
'category'
,
categoryID
);
this
.
set
(
'category_ids'
,
[
categoryID
]);
},
updateSeatData
:
function
()
{
var
seat_data
,
seats
=
this
.
get
(
'seats'
);
...
...
@@ -230,27 +237,37 @@ define([
'invoice_payment_date'
:
invoice
.
payment_date
,
'tax_deducted_source'
:
invoice
.
tax_deducted_source
,
'tax_deduction'
:
tax_deducted
,
});
});
},
save
:
function
(
options
)
{
save
:
function
(
attributes
,
options
)
{
_
.
defaults
(
options
||
(
options
=
{}),
{
// The API requires a CSRF token for all POST requests using session authentication.
headers
:
{
'X-CSRFToken'
:
Cookies
.
get
(
'ecommerce_csrftoken'
)},
contentType
:
'application/json'
});
this
.
set
(
'start_date'
,
moment
.
utc
(
this
.
get
(
'start_date'
)));
this
.
set
(
'end_date'
,
moment
.
utc
(
this
.
get
(
'end_date'
)));
this
.
set
(
'category_ids'
,
[
this
.
get
(
'category'
)]);
if
(
!
options
.
patch
){
this
.
set
(
'start_date'
,
moment
.
utc
(
this
.
get
(
'start_date'
)));
this
.
set
(
'end_date'
,
moment
.
utc
(
this
.
get
(
'end_date'
)));
if
(
this
.
get
(
'coupon_type'
)
===
'Enrollment code'
)
{
this
.
set
(
'benefit_type'
,
'Percentage'
);
this
.
set
(
'benefit_value'
,
100
);
}
options
.
data
=
JSON
.
stringify
(
this
.
toJSON
());
}
else
{
if
(
_
.
has
(
attributes
,
'start_date'
))
{
attributes
.
start_date
=
moment
.
utc
(
attributes
.
start_date
);
}
if
(
this
.
get
(
'coupon_type'
)
===
'Enrollment code'
)
{
this
.
set
(
'benefit_type'
,
'Percentage'
);
this
.
set
(
'benefit_value'
,
100
);
if
(
_
.
has
(
attributes
,
'end_date'
)
)
{
attributes
.
end_date
=
moment
.
utc
(
attributes
.
end_date
);
}
}
options
.
data
=
JSON
.
stringify
(
this
.
toJSON
());
return
this
.
_super
(
null
,
options
);
return
this
.
_super
(
attributes
,
options
);
}
});
}
...
...
ecommerce/static/js/test/specs/models/coupon_model_spec.js
View file @
af9f2d4a
...
...
@@ -145,6 +145,21 @@ define([
ajaxData
=
JSON
.
parse
(
args
[
0
].
data
);
expect
(
ajaxData
.
quantity
).
toEqual
(
1
);
});
it
(
'should format start and end date if they are patch updated'
,
function
()
{
var
model
=
Coupon
.
findOrCreate
(
discountCodeData
,
{
parse
:
true
});
spyOn
(
moment
,
'utc'
);
model
.
save
(
{
start_date
:
'2015-11-11T00:00:00Z'
,
end_date
:
'2016-11-11T00:00:00Z'
},
{
patch
:
true
}
);
expect
(
moment
.
utc
).
toHaveBeenCalledWith
(
'2015-11-11T00:00:00Z'
);
expect
(
moment
.
utc
).
toHaveBeenCalledWith
(
'2016-11-11T00:00:00Z'
);
});
});
});
...
...
ecommerce/static/js/test/specs/views/coupon_edit_view_spec.js
View file @
af9f2d4a
...
...
@@ -73,7 +73,7 @@ define([
expect
(
view
.
$el
.
find
(
'[name=code]'
).
val
()).
toEqual
(
model
.
get
(
'code'
));
});
});
describe
(
'Coupon with invoice data'
,
function
()
{
beforeEach
(
function
()
{
model
=
Coupon
.
findOrCreate
(
invoice_coupon_data
,
{
parse
:
true
});
...
...
@@ -96,6 +96,17 @@ define([
expect
(
view
.
$el
.
find
(
'[name=tax_deducted_source_value]'
).
val
())
.
toEqual
(
model
.
get
(
'tax_deducted_source'
));
});
it
(
'should patch save the model when form is in editing mode and has editable attributes'
,
function
()
{
var
formView
=
view
.
formView
;
spyOn
(
formView
.
model
,
'save'
);
spyOn
(
formView
.
model
,
'isValid'
).
and
.
returnValue
(
true
);
expect
(
formView
.
modelServerState
).
toEqual
(
model
.
pick
(
formView
.
editableAttributes
));
formView
.
model
.
set
(
'title'
,
'Test Title'
);
formView
.
submit
(
$
.
Event
(
'click'
));
expect
(
model
.
save
).
toHaveBeenCalled
();
});
});
});
}
...
...
ecommerce/static/js/test/specs/views/form_view_spec.js
View file @
af9f2d4a
...
...
@@ -139,7 +139,6 @@ define([
errorObj
=
{
responseJSON
:
{
error
:
'An error occurred while saving the data.'
}};
testErrorResponse
();
});
});
}
);
ecommerce/static/js/views/coupon_form_view.js
View file @
af9f2d4a
...
...
@@ -79,6 +79,10 @@ define([
},
setOptions
:
{
validate
:
true
},
onSet
:
function
(
val
)
{
this
.
model
.
set
(
'category_ids'
,
[
val
]);
return
val
;
}
},
'input[name=title]'
:
{
...
...
@@ -226,6 +230,27 @@ define([
this
.
editing
=
options
.
editing
||
false
;
this
.
hiddenClass
=
'hidden'
;
if
(
this
.
editing
)
{
this
.
editableAttributes
=
[
'benefit_value'
,
'catalog_query'
,
'category_ids'
,
'client'
,
'course_seat_types'
,
'end_date'
,
'invoice_discount_type'
,
'invoice_discount_value'
,
'invoice_number'
,
'invoice_payment_date'
,
'invoice_type'
,
'note'
,
'price'
,
'start_date'
,
'tax_deducted_source'
,
'title'
,
];
}
this
.
dynamic_catalog_view
=
new
DynamicCatalogView
({
'query'
:
this
.
model
.
get
(
'catalog_query'
),
'seat_types'
:
this
.
model
.
get
(
'course_seat_types'
)
...
...
@@ -303,7 +328,7 @@ define([
this
.
formGroup
(
'[name=code]'
).
addClass
(
this
.
hiddenClass
);
}
},
toggleInvoiceFields
:
function
()
{
var
invoice_type
=
this
.
$
(
'[name=invoice_type]:checked'
).
val
(),
prepaid_fields
=
[
...
...
@@ -523,7 +548,6 @@ define([
this
.
$alerts
=
this
.
$
(
'.alerts'
);
if
(
this
.
editing
)
{
this
.
$
(
'select[name=category]'
).
val
(
this
.
model
.
get
(
'categories'
)[
0
].
id
).
trigger
(
'change'
);
this
.
disableNonEditableFields
();
this
.
toggleCouponTypeField
();
this
.
toggleVoucherTypeField
();
...
...
ecommerce/static/js/views/form_view.js
View file @
af9f2d4a
...
...
@@ -28,6 +28,10 @@ define([
initialize
:
function
()
{
this
.
alertViews
=
[];
if
(
this
.
editing
&&
_
.
has
(
this
,
'editableAttributes'
))
{
this
.
modelServerState
=
this
.
model
.
pick
(
this
.
editableAttributes
);
}
// Enable validation
Utils
.
bindValidation
(
this
);
},
...
...
@@ -149,7 +153,9 @@ define([
self
=
this
,
courseId
=
$
(
'input[name=id]'
).
val
(),
btnSavingContent
=
'<i class="fa fa-spinner fa-spin" aria-hidden="true"></i> '
+
gettext
(
'Saving...'
);
gettext
(
'Saving...'
),
onSaveComplete
,
onSaveError
;
e
.
preventDefault
();
...
...
@@ -174,33 +180,54 @@ define([
// Disable all buttons by setting the attribute (for <button>) and class (for <a>)
$buttons
.
attr
(
'disabled'
,
'disabled'
).
addClass
(
'disabled'
);
this
.
model
.
save
({
complete
:
function
()
{
// Restore the button text
$submitButton
.
text
(
btnDefaultText
);
onSaveComplete
=
function
()
{
// Restore the button text
$submitButton
.
text
(
btnDefaultText
);
// Re-enable the buttons
$buttons
.
removeAttr
(
'disabled'
).
removeClass
(
'disabled'
);
},
success
:
this
.
saveSuccess
.
bind
(
this
),
error
:
function
(
model
,
response
)
{
var
message
=
gettext
(
'An error occurred while saving the data.'
);
if
(
response
.
responseJSON
&&
response
.
responseJSON
.
error
)
{
message
=
response
.
responseJSON
.
error
;
// Log the error to the console for debugging purposes
console
.
error
(
message
);
}
else
{
// Log the error to the console for debugging purposes
console
.
error
(
response
.
responseText
);
}
// Re-enable the buttons
$buttons
.
removeAttr
(
'disabled'
).
removeClass
(
'disabled'
);
};
onSaveError
=
function
(
model
,
response
)
{
var
message
=
gettext
(
'An error occurred while saving the data.'
);
self
.
clearAlerts
();
self
.
renderAlert
(
'danger'
,
message
);
self
.
$el
.
animate
({
scrollTop
:
0
},
'slow'
);
if
(
response
.
responseJSON
&&
response
.
responseJSON
.
error
)
{
message
=
response
.
responseJSON
.
error
;
// Log the error to the console for debugging purposes
console
.
error
(
message
);
}
else
{
// Log the error to the console for debugging purposes
console
.
error
(
response
.
responseText
);
}
});
self
.
clearAlerts
();
self
.
renderAlert
(
'danger'
,
message
);
self
.
$el
.
animate
({
scrollTop
:
0
},
'slow'
);
};
if
(
this
.
editing
&&
_
.
has
(
this
,
'editableAttributes'
))
{
var
editableAttributes
=
this
.
model
.
pick
(
this
.
editableAttributes
),
changedAttributes
=
_
.
omit
(
editableAttributes
,
function
(
value
,
key
)
{
return
value
===
this
.
modelServerState
[
key
];
},
this
);
this
.
model
.
save
(
changedAttributes
,
{
complete
:
onSaveComplete
,
error
:
onSaveError
,
patch
:
true
,
success
:
this
.
saveSuccess
.
bind
(
this
)
}
);
}
else
{
this
.
model
.
save
({
complete
:
onSaveComplete
,
success
:
this
.
saveSuccess
.
bind
(
this
),
error
:
onSaveError
});
}
return
this
;
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment