Skip to content

Commit

Permalink
✨(backend) add offer and price fields to courseRun
Browse files Browse the repository at this point in the history
  • Loading branch information
Tiago-Salles committed Dec 9, 2024
1 parent 0e67e48 commit 688a379
Show file tree
Hide file tree
Showing 17 changed files with 1,189 additions and 38 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- Add offer and price fields to courseRun displayed at admin view
- Add Additional Information section for a category and
use them in a course page
- Added new page extension `MainMenuEntry`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, Configura
environ_prefix=None,
)

# Course run price currency value that would be shown on course detail page
RICHIE_DEFAULT_COURSE_RUN_PRICE_CURRENCY = values.Value(
"EUR",
environ_name="RICHIE_DEFAULT_COURSE_RUN_PRICE_CURRENCY",
environ_prefix=None,
)

# Internationalization
TIME_ZONE = "Europe/Paris"
USE_I18N = True
Expand Down
5 changes: 5 additions & 0 deletions src/frontend/js/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export interface CourseRun {
title?: string;
snapshot?: string;
display_mode: CourseRunDisplayMode;
price?: number;
price_currency?: string;
offer?: string;
certificate_price?: number;
certificate_offer?: string;
}

export enum Priority {
Expand Down
16 changes: 16 additions & 0 deletions src/frontend/js/utils/test/factories/richie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ export const CourseStateFutureOpenFactory = factory<CourseState>(() => {
});

export const CourseRunFactory = factory<CourseRun>(() => {

var offers = ["PAID", "FREE", "PARTIALLY_FREE", "SUBSCRIPTION"];
const offer = offers[Math.floor(Math.random() * offers.length)];

offers = ["PAID", "FREE", "SUBSCRIPTION"];
const certificateOffer = offers[Math.floor(Math.random() * offers.length)];

const currency = faker.finance.currency().code;
const price = ["FREE", "PARTIALLY_FREE"].includes(offer) ? 0 : parseFloat(faker.finance.amount({ min: 1, max: 100, symbol: currency, autoFormat: true }));
const cerficatePrice = certificateOffer == "FREE" ? 0 : parseFloat(faker.finance.amount({ min: 1, max: 100, symbol: currency, autoFormat: true }));

return {
id: faker.number.int(),
resource_link: FactoryHelper.unique(faker.internet.url),
Expand All @@ -58,6 +69,11 @@ export const CourseRunFactory = factory<CourseRun>(() => {
dashboard_link: null,
title: faker.lorem.sentence(3),
display_mode: CourseRunDisplayMode.DETAILED,
price: price,
price_currency: currency,
offer: offer,
certificate_price: cerficatePrice,
certificate_offer: certificateOffer,
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,51 @@ const messages = defineMessages({
description: 'Course date of an opened course run block',
defaultMessage: 'From {startDate} {endDate, select, undefined {} other {to {endDate}}}',
},
coursePrice: {
id: 'components.SyllabusCourseRun.coursePrice',
description: 'Title of the course enrollment price section of an opened course run block',
defaultMessage: 'Enrollment price',
},
certificationPrice: {
id: 'components.SyllabusCourseRun.certificationPrice',
description: 'Title of the certification price section of an opened course run block',
defaultMessage: 'Certification price',
},
coursePaidOffer: {
id: 'components.SyllabusCourseRun.coursePaidOffer',
description: 'Message for the paid course offer of an opened course run block',
defaultMessage: 'The course content is paid.',
},
courseFreeOffer: {
id: 'components.SyllabusCourseRun.courseFreeOffer',
description: 'Message for the free course offer of an opened course run block',
defaultMessage: 'The course content is free.',
},
coursePartiallyFree: {
id: 'components.SyllabusCourseRun.coursePartiallyFree',
description: 'Message for the partially free course offer of an opened course run block',
defaultMessage: 'The course content is free.',
},
courseSubscriptionOffer: {
id: 'components.SyllabusCourseRun.courseSubscriptionOffer',
description: 'Message for the subscription course offer of an opened course run block',
defaultMessage: 'Subscribe to access the course content.',
},
certificatePaidOffer: {
id: 'components.SyllabusCourseRun.certificatePaidOffer',
description: 'Messagge for the paid certification offer of an opened course run block',
defaultMessage: 'The certification process is paid.',
},
certificateFreeOffer: {
id: 'components.SyllabusCourseRun.certificateFreeOffer',
description: 'Message for the free certification offer of an opened course run block',
defaultMessage: 'The certification process is free.',
},
certificateSubscriptionOffer: {
id: 'components.SyllabusCourseRun.certificateSubscriptionOffer',
description: 'Message for the subscription certification offer of an opened course run block',
defaultMessage: 'The certification process is offered through subscription.',
},
});

const OpenedCourseRun = ({
Expand All @@ -63,6 +108,44 @@ const OpenedCourseRun = ({
const enrollmentEnd = courseRun.enrollment_end ? formatDate(courseRun.enrollment_end) : '...';
const start = courseRun.start ? formatDate(courseRun.start) : '...';
const end = courseRun.end ? formatDate(courseRun.end) : '...';
var courseOfferMessage = undefined;
var certificationOfferMessage = undefined;
var enrollmentPrice = "";
var certificatePrice = "";

if(courseRun.offer){
const offer = courseRun.offer.toUpperCase().replaceAll(" ", "_");
courseOfferMessage = {
"PAID": messages.coursePaidOffer,
"FREE": messages.courseFreeOffer,
"PARTIALLY_FREE": messages.coursePartiallyFree,
"SUBSCRIPTION": messages.courseSubscriptionOffer,
}[offer]

if((courseRun.price ?? -1) >= 0){
enrollmentPrice = intl.formatNumber(courseRun.price!, {
style: 'currency',
currency: courseRun.price_currency,
});
}
}

if(courseRun.certificate_offer){
const certificationOffer = courseRun.certificate_offer.toUpperCase().replaceAll(" ", "")
certificationOfferMessage = {
"PAID": messages.certificatePaidOffer,
"FREE": messages.certificateFreeOffer,
"SUBSCRIPTION": messages.certificateSubscriptionOffer,
}[certificationOffer]

if((courseRun.certificate_price ?? -1) >= 0){
certificatePrice = intl.formatNumber(courseRun.certificate_price!, {
style: 'currency',
currency: courseRun.price_currency,
})
}
}

return (
<>
{courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
Expand Down Expand Up @@ -99,6 +182,26 @@ const OpenedCourseRun = ({
<dd>{IntlHelper.getLocalizedLanguages(courseRun.languages, intl)}</dd>
</>
)}
{courseOfferMessage && (
<>
<dt>
<FormattedMessage {...messages.coursePrice} />
</dt>
<dd>
<FormattedMessage {...courseOfferMessage} /><br/>{`${enrollmentPrice}`}
</dd>
</>
)}
{certificationOfferMessage && (
<>
<dt>
<FormattedMessage {...messages.certificationPrice} />
</dt>
<dd>
<FormattedMessage {...certificationOfferMessage} /><br/>{`${certificatePrice}`}
</dd>
</>
)}
</dl>
{findLmsBackend(courseRun.resource_link) ? (
<CourseRunEnrollment courseRun={courseRun} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,51 @@ const messages = defineMessages({
description: 'Self paced course run block with no end date',
defaultMessage: 'Available',
},
coursePrice: {
id: 'components.SyllabusCourseRunCompacted.coursePrice',
description: 'Title of the course enrollment price section of an opened course run block',
defaultMessage: 'Enrollment price',
},
certificationPrice: {
id: 'components.SyllabusCourseRunCompacted.certificationPrice',
description: 'Title of the certification price section of an opened course run block',
defaultMessage: 'Certification price',
},
coursePaidOffer: {
id: 'components.SyllabusCourseRunCompacted.coursePaidOffer',
description: 'Message for the paid course offer of an opened course run block',
defaultMessage: 'The course content is paid.',
},
courseFreeOffer: {
id: 'components.SyllabusCourseRunCompacted.courseFreeOffer',
description: 'Message for the free course offer of an opened course run block',
defaultMessage: 'The course content is free.',
},
coursePartiallyFree: {
id: 'components.SyllabusCourseRunCompacted.coursePartiallyFree',
description: 'Message for the partially free course offer of an opened course run block',
defaultMessage: 'The course content is free.',
},
courseSubscriptionOffer: {
id: 'components.SyllabusCourseRunCompacted.courseSubscriptionOffer',
description: 'Message for the subscription course offer of an opened course run block',
defaultMessage: 'Subscribe to access the course content.',
},
certificatePaidOffer: {
id: 'components.SyllabusCourseRunCompacted.certificatePaidOffer',
description: 'Messagge for the paid certification offer of an opened course run block',
defaultMessage: 'The certification process is paid.',
},
certificateFreeOffer: {
id: 'components.SyllabusCourseRunCompacted.certificateFreeOffer',
description: 'Message for the free certification offer of an opened course run block',
defaultMessage: 'The certification process is free.',
},
certificateSubscriptionOffer: {
id: 'components.SyllabusCourseRunCompacted.certificateSubscriptionOffer',
description: 'Message for the subscription certification offer of an opened course run block',
defaultMessage: 'The certification process is offered through subscription.',
},
});

const OpenedSelfPacedCourseRun = ({
Expand All @@ -54,6 +99,44 @@ const OpenedSelfPacedCourseRun = ({
const intl = useIntl();
const end = courseRun.end ? formatDate(courseRun.end) : '...';
const hasEndDate = end !== '...';
var courseOfferMessage = undefined;
var certificationOfferMessage = undefined;
var enrollmentPrice = "";
var certificatePrice = "";

if(courseRun.offer){
const offer = courseRun.offer.toUpperCase().replaceAll(" ", "_");
courseOfferMessage = {
"PAID": messages.coursePaidOffer,
"FREE": messages.courseFreeOffer,
"PARTIALLY_FREE": messages.coursePartiallyFree,
"SUBSCRIPTION": messages.courseSubscriptionOffer,
}[offer]

if((courseRun.price ?? -1) >= 0){
enrollmentPrice = intl.formatNumber(courseRun.price!, {
style: 'currency',
currency: courseRun.price_currency,
});
}
}

if(courseRun.certificate_offer){
const certificationOffer = courseRun.certificate_offer.toUpperCase().replaceAll(" ", "")
certificationOfferMessage = {
"PAID": messages.certificatePaidOffer,
"FREE": messages.certificateFreeOffer,
"SUBSCRIPTION": messages.certificateSubscriptionOffer,
}[certificationOffer]

if((courseRun.certificate_price ?? -1) >= 0){
certificatePrice = intl.formatNumber(courseRun.certificate_price!, {
style: 'currency',
currency: courseRun.price_currency,
})
}
}

return (
<>
{courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
Expand Down Expand Up @@ -83,6 +166,26 @@ const OpenedSelfPacedCourseRun = ({
<dd>{IntlHelper.getLocalizedLanguages(courseRun.languages, intl)}</dd>
</>
)}
{courseOfferMessage && (
<>
<dt>
<FormattedMessage {...messages.coursePrice} />
</dt>
<dd>
<FormattedMessage {...courseOfferMessage} /><br/>{`${enrollmentPrice}`}
</dd>
</>
)}
{certificationOfferMessage && (
<>
<dt>
<FormattedMessage {...messages.certificationPrice} />
</dt>
<dd>
<FormattedMessage {...certificationOfferMessage} /><br/>{`${certificatePrice}`}
</dd>
</>
)}
</dl>
{findLmsBackend(courseRun.resource_link) ? (
<CourseRunEnrollment courseRun={courseRun} />
Expand Down
Loading

0 comments on commit 688a379

Please sign in to comment.