Skip to content

Commit

Permalink
✨(courses) add price_currency, offer and price fields to courseRun
Browse files Browse the repository at this point in the history
- Add price fields to CourseRun
- Show price fields on the screen
  • Loading branch information
Tiago-Salles committed Dec 16, 2024
1 parent 0e67e48 commit dc05c7c
Show file tree
Hide file tree
Showing 17 changed files with 1,174 additions and 41 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
17 changes: 17 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,18 @@ export const CourseStateFutureOpenFactory = factory<CourseState>(() => {
});

export const CourseRunFactory = factory<CourseRun>(() => {
let 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 +70,11 @@ export const CourseRunFactory = factory<CourseRun>(() => {
dashboard_link: null,
title: faker.lorem.sentence(3),
display_mode: CourseRunDisplayMode.DETAILED,
price,
price_currency: currency,
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) : '...';
let courseOfferMessage = null;
let certificationOfferMessage = null;
let enrollmentPrice = '';
let 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,30 @@ 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 !== '...';
let courseOfferMessage = null;
let certificationOfferMessage = null;
let enrollmentPrice = '';
let 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,30 @@ 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 dc05c7c

Please sign in to comment.