fix(medusa): gift card values & taxes are calculated correctly (#2777)

* chore: tax_rate is added to giftcards

* chore: minor

* chore: update gift card tax calculations to look at giftCard.tax_rate

* chore: gift card transactions use tax rate from gift card for legacy

* fix: gift cart total check for transaction should check the length

* chore: use tax exclusive cost + use giftcard tax rate for gctransactions + refactor

* chore: fix integration test

* chore: address issues brought up in comments

* chore: move gift card creation as a part of order service on order placed

* chore: add type handling for gift card creation

* chore: fix specs

* chore: use taxLines to calculate tax of a gift card

* chore: specs for line items containing gift cards and without

* chore: add integration specs + fix tax rate bug

* chore: round totaling + add GC application specs

* chore: cleanup trialables

* chore: write migration script to backfill gift cards with null tax_rate

* chore: update legacy totals service for gift cards

* chore: add changeset

* chore: address PR review changes

* chore: fix tests based on new totals calc

* chore: address review comments

Co-authored-by: adrien2p <adrien.deperetti@gmail.com>
Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Riqwan Thamir
2022-12-20 22:24:25 +01:00
committed by GitHub
parent 3113d8024f
commit 8a60a73389
26 changed files with 968 additions and 181 deletions

View File

@@ -0,0 +1,10 @@
export const typeormConfig = {
type: process.env.TYPEORM_CONNECTION,
url: process.env.TYPEORM_URL,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE,
migrations: [process.env.TYPEORM_MIGRATIONS as string],
entities: [process.env.TYPEORM_ENTITIES],
logging: true,
}

View File

@@ -11,18 +11,9 @@ import {
import { DiscountConditionProduct } from "../models/discount-condition-product"
import { DiscountRule } from "../models/discount-rule"
import { DiscountConditionRepository } from "../repositories/discount-condition"
dotenv.config()
import { typeormConfig } from "./db-config"
const typeormConfig = {
type: process.env.TYPEORM_CONNECTION,
url: process.env.TYPEORM_URL,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE,
migrations: [process.env.TYPEORM_MIGRATIONS as string],
entities: [process.env.TYPEORM_ENTITIES],
logging: true,
}
dotenv.config()
const migrate = async function ({ typeormConfig }): Promise<void> {
const connection = await createConnection(typeormConfig)

View File

@@ -0,0 +1,101 @@
import dotenv from "dotenv"
import { createConnection, IsNull } from "typeorm"
import Logger from "../loaders/logger"
import { GiftCard } from "../models/gift-card"
import { typeormConfig } from "./db-config"
dotenv.config()
const BATCH_SIZE = 1000
const migrationName = 'gift-card-tax-rate-migration'
Logger.info(`typeormConfig: ${JSON.stringify(typeormConfig)}`)
const migrate = async function ({ typeormConfig }): Promise<void> {
const connection = await createConnection(typeormConfig)
await connection.transaction(async (manager) => {
let offset = 0
// Get all the GiftCards where the gift_card.tax_rate is null
const giftCardsCount = await manager
.createQueryBuilder()
.withDeleted()
.from(GiftCard, "gc")
.select("gc.id")
.where("gc.tax_rate IS NULL")
.getCount()
const totalBatches = Math.ceil(giftCardsCount / BATCH_SIZE)
if (totalBatches == 0) {
Logger.info(`${migrationName}: No records to update, skipping migration!`)
return
}
Logger.info(`${migrationName}: Running migration for ${giftCardsCount} GiftCards`)
Logger.info(`${migrationName}: Running migration in ${totalBatches} batch(es) of ${BATCH_SIZE}`)
for (let batch = 1; batch <= totalBatches; batch++) {
Logger.info(`${migrationName}: Starting batch ${batch} of ${totalBatches}`)
// Get all the GiftCards and its region where the gift_card.tax_rate is null
const giftCardRegionRecords = await manager
.createQueryBuilder()
.withDeleted()
.from(GiftCard, "gc")
.select("gc.id, gc.region_id, gc.tax_rate, r.tax_rate as region_tax_rate")
.innerJoin("region", "r", "gc.region_id = r.id")
.where("gc.tax_rate IS NULL")
.limit(BATCH_SIZE)
.offset(offset)
.getRawMany()
// Loop through each gift card record and update the value of gift_card.tax_rate
// with region.tax_rate value
giftCardRegionRecords.forEach(async (gcr) => {
await manager
.createQueryBuilder()
.update(GiftCard)
.set({ tax_rate: gcr.region_tax_rate })
.where("id = :id", { id: gcr.id })
.execute()
})
offset += BATCH_SIZE
Logger.info(`${migrationName}: Finished batch ${batch} of ${totalBatches}`)
}
const recordsFailedToBackfill = await manager
.createQueryBuilder()
.withDeleted()
.from(GiftCard, "gc")
.select("gc.id")
.where("gc.tax_rate IS NULL")
.getCount()
if (recordsFailedToBackfill == 0) {
Logger.info(`${migrationName}: successfully ran for ${giftCardsCount} GiftCards`)
} else {
Logger.info(`${migrationName}: ${recordsFailedToBackfill} GiftCards have no tax_rate set`)
Logger.info(`${migrationName}: 1. Check if all GiftCards have a region associated with it`)
Logger.info(`${migrationName}: If not, they need to be associated with a region & re-run migration`)
Logger.info(`${migrationName}: 2. Check if regions have a tax_rate added to it`)
Logger.info(`${migrationName}: If regions intentionally have no tax_rate, this can be ignored`)
Logger.info(`${migrationName}: If not, add a tax_rate to region & re-run migration`)
}
})
}
migrate({ typeormConfig })
.then(() => {
Logger.info("Database migration completed")
process.exit()
}).catch((err) => {
Logger.error(`Database migration failed - ${JSON.stringify(err)}`)
process.exit(1)
})
export default migrate

View File

@@ -3,18 +3,9 @@ import { createConnection, SelectQueryBuilder } from "typeorm"
import Logger from "../loaders/logger"
import { LineItem } from "../models/line-item"
import { LineItemAdjustment } from "../models/line-item-adjustment"
dotenv.config()
import { typeormConfig } from "./db-config"
const typeormConfig = {
type: process.env.TYPEORM_CONNECTION,
url: process.env.TYPEORM_URL,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE,
migrations: [process.env.TYPEORM_MIGRATIONS as string],
entities: [process.env.TYPEORM_ENTITIES],
logging: true,
}
dotenv.config()
const migrate = async function({ typeormConfig }) {
const connection = await createConnection(typeormConfig)

View File

@@ -3,20 +3,10 @@ import { createConnection } from "typeorm"
import Logger from "../loaders/logger"
import { Product } from "../models/product"
import { Store } from "../models/store"
import { typeormConfig } from "./db-config"
dotenv.config()
const typeormConfig = {
type: process.env.TYPEORM_CONNECTION,
url: process.env.TYPEORM_URL,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE,
migrations: [process.env.TYPEORM_MIGRATIONS as string],
entities: [process.env.TYPEORM_ENTITIES],
logging: true,
}
const migrate = async function ({ typeormConfig }): Promise<void> {
const connection = await createConnection(typeormConfig)