@@ -29,6 +29,7 @@ import {
2929 groupBy ,
3030 InjectManager ,
3131 InjectTransactionManager ,
32+ isDefined ,
3233 isPresent ,
3334 isString ,
3435 MathBN ,
@@ -370,7 +371,6 @@ export default class PricingModuleService
370371 }
371372 }
372373
373-
374374 pricesSetPricesMap . set ( key , { calculatedPrice, originalPrice } )
375375 priceIds . push (
376376 ...( deduplicate (
@@ -535,6 +535,7 @@ export default class PricingModuleService
535535 @MedusaContext ( ) sharedContext : Context = { }
536536 ) : Promise < PriceSetDTO | PriceSetDTO [ ] > {
537537 const input = Array . isArray ( data ) ? data : [ data ]
538+
538539 const forUpdate = input . filter (
539540 ( priceSet ) : priceSet is ServiceTypes . UpdatePriceSetInput => ! ! priceSet . id
540541 )
@@ -621,73 +622,215 @@ export default class PricingModuleService
621622 data : ServiceTypes . UpdatePriceSetInput [ ] ,
622623 @MedusaContext ( ) sharedContext : Context = { }
623624 ) : Promise < InferEntityType < typeof PriceSet > [ ] > {
624- // TODO: Since money IDs are rarely passed, this will delete all previous data and insert new entries.
625- // We can make the `insert` inside upsertWithReplace do an `upsert` instead to avoid this
626625 const normalizedData = await this . normalizeUpdateData ( data )
627626
628- const priceListPrices = await this . priceService_ . list ( {
629- price_set_id : normalizedData . map ( ( { id } ) => id ) ,
630- price_list_id : { $ne : null } ,
631- } )
627+ const priceSetIds = normalizedData . map ( ( { id } ) => id )
628+ const existingPrices = await this . priceService_ . list (
629+ {
630+ price_set_id : priceSetIds ,
631+ price_list_id : null ,
632+ } ,
633+ {
634+ relations : [ "price_rules" ] ,
635+ take : null ,
636+ } ,
637+ sharedContext
638+ )
639+
640+ const existingPricesMap = new Map < string , InferEntityType < typeof Price > > (
641+ existingPrices . map ( ( p ) => [ p . id , p ] )
642+ )
632643
633644 const prices = normalizedData . flatMap ( ( priceSet ) => priceSet . prices || [ ] )
634- const { entities : upsertedPrices , performedActions } =
635- await this . priceService_ . upsertWithReplace (
636- prices ,
637- { relations : [ "price_rules" ] } ,
645+
646+ const pricesToCreate = prices . filter (
647+ ( price ) => ! price . id || ! existingPricesMap . has ( price . id )
648+ )
649+ const pricesToUpdate = prices . filter (
650+ ( price ) => price . id && existingPricesMap . has ( price . id )
651+ )
652+
653+ const incomingPriceIds = new Set ( prices . map ( ( p ) => p . id ) . filter ( Boolean ) )
654+ const pricesToDelete = existingPrices
655+ . filter ( ( existingPrice ) => ! incomingPriceIds . has ( existingPrice . id ) )
656+ . map ( ( p ) => p . id )
657+
658+ let createdPrices : InferEntityType < typeof Price > [ ] = [ ]
659+ let updatedPrices : InferEntityType < typeof Price > [ ] = [ ]
660+
661+ if ( pricesToCreate . length > 0 ) {
662+ createdPrices = await this . priceService_ . create (
663+ pricesToCreate . map ( ( price ) => {
664+ price . price_rules ??= [ ]
665+ return price
666+ } ) ,
638667 sharedContext
639668 )
669+ }
640670
641- composeAllEvents ( {
642- eventBuilders,
643- performedActions,
644- sharedContext,
645- } )
671+ if ( pricesToUpdate . length > 0 ) {
672+ // Handle price rules for updated prices
673+ for ( const priceToUpdate of pricesToUpdate ) {
674+ const existingPrice = existingPricesMap . get ( priceToUpdate . id ! )
675+
676+ if ( priceToUpdate . price_rules ?. length ) {
677+ const existingPriceRules = existingPrice ?. price_rules || [ ]
678+
679+ // Separate price rules for create, update, delete
680+ const priceRulesToCreate = priceToUpdate . price_rules . filter (
681+ ( rule ) => ! ( "id" in rule )
682+ )
683+ const priceRulesToUpdate = priceToUpdate . price_rules . filter (
684+ ( rule ) => "id" in rule
685+ )
686+
687+ const incomingPriceRuleIds = new Set (
688+ priceToUpdate . price_rules
689+ . map ( ( r ) => "id" in r && r . id )
690+ . filter ( Boolean )
691+ )
692+ const priceRulesToDelete = existingPriceRules
693+ . filter (
694+ ( existingRule ) => ! incomingPriceRuleIds . has ( existingRule . id )
695+ )
696+ . map ( ( r ) => r . id )
697+
698+ let createdPriceRules : InferEntityType < typeof PriceRule > [ ] = [ ]
699+ let updatedPriceRules : InferEntityType < typeof PriceRule > [ ] = [ ]
700+
701+ // Bulk operations for price rules
702+ if ( priceRulesToCreate . length > 0 ) {
703+ createdPriceRules = await this . priceRuleService_ . create (
704+ priceRulesToCreate . map ( ( rule ) => ( {
705+ ...rule ,
706+ price_id : priceToUpdate . id ,
707+ } ) ) ,
708+ sharedContext
709+ )
710+ eventBuilders . createdPriceRule ( {
711+ data : createdPriceRules . map ( ( r ) => ( { id : r . id } ) ) ,
712+ sharedContext,
713+ } )
714+ }
646715
647- const priceSetsToUpsert = normalizedData . map ( ( priceSet ) => {
648- const { prices, ...rest } = priceSet
649- return {
650- ...rest ,
651- prices : [
652- ...upsertedPrices
653- . filter ( ( p ) => p . price_set_id === priceSet . id )
654- . map ( ( price ) => {
655- // @ts -ignore
656- delete price . price_rules
657- return price
658- } ) ,
659- ...priceListPrices
660- . filter ( ( p ) => p . price_set_id === priceSet . id )
661- . map ( ( price ) => ( {
662- id : price . id ,
663- amount : price . amount ,
664- price_set_id : price . price_set_id ,
665- price_list_id : price . price_list_id ,
666- } ) ) ,
667- ] ,
716+ if ( priceRulesToUpdate . length > 0 ) {
717+ updatedPriceRules = await this . priceRuleService_ . update (
718+ priceRulesToUpdate ,
719+ sharedContext
720+ )
721+ eventBuilders . updatedPriceRule ( {
722+ data : updatedPriceRules . map ( ( r ) => ( { id : r . id } ) ) ,
723+ sharedContext,
724+ } )
725+ }
726+
727+ if ( priceRulesToDelete . length > 0 ) {
728+ await this . priceRuleService_ . delete (
729+ priceRulesToDelete ,
730+ sharedContext
731+ )
732+ eventBuilders . deletedPriceRule ( {
733+ data : priceRulesToDelete . map ( ( id ) => ( { id } ) ) ,
734+ sharedContext,
735+ } )
736+ }
737+
738+ const upsertedPriceRules = [
739+ ...createdPriceRules ,
740+ ...updatedPriceRules ,
741+ ]
742+
743+ priceToUpdate . price_rules = upsertedPriceRules
744+ ; ( priceToUpdate as InferEntityType < typeof Price > ) . rules_count =
745+ upsertedPriceRules . length
746+ } else if (
747+ // In the case price_rules is provided but without any rules, we delete the existing rules
748+ isDefined ( priceToUpdate . price_rules ) &&
749+ priceToUpdate . price_rules . length === 0
750+ ) {
751+ const priceRuleToDelete = existingPrice ?. price_rules ?. map ( ( r ) => r . id )
752+
753+ if ( priceRuleToDelete ?. length ) {
754+ await this . priceRuleService_ . delete (
755+ priceRuleToDelete ,
756+ sharedContext
757+ )
758+ eventBuilders . deletedPriceRule ( {
759+ data : priceRuleToDelete . map ( ( id ) => ( { id } ) ) ,
760+ sharedContext,
761+ } )
762+ }
763+
764+ ; ( priceToUpdate as InferEntityType < typeof Price > ) . rules_count = 0
765+ } else {
766+ // @ts -expect-error - we want to delete the rules_count property in any case even if provided by mistake
767+ delete ( priceToUpdate as InferEntityType < typeof Price > ) . rules_count
768+ }
769+ // We don't want to persist the price_rules in the database through the price service as it would not work
770+ delete priceToUpdate . price_rules
668771 }
669- } )
670772
671- const { entities : priceSets , performedActions : priceSetPerformedActions } =
672- await this . priceSetService_ . upsertWithReplace (
673- priceSetsToUpsert ,
674- { relations : [ "prices" ] } ,
773+ updatedPrices = await this . priceService_ . update (
774+ pricesToUpdate ,
675775 sharedContext
676776 )
777+ }
677778
678- composeAllEvents ( {
679- eventBuilders,
680- performedActions : priceSetPerformedActions ,
681- sharedContext,
779+ if ( pricesToDelete . length > 0 ) {
780+ await this . priceService_ . delete ( pricesToDelete , sharedContext )
781+ }
782+
783+ if ( createdPrices . length > 0 ) {
784+ eventBuilders . createdPrice ( {
785+ data : createdPrices . map ( ( p ) => ( { id : p . id } ) ) ,
786+ sharedContext,
787+ } )
788+ }
789+
790+ if ( updatedPrices . length > 0 ) {
791+ eventBuilders . updatedPrice ( {
792+ data : updatedPrices . map ( ( p ) => ( { id : p . id } ) ) ,
793+ sharedContext,
794+ } )
795+ }
796+
797+ if ( pricesToDelete . length > 0 ) {
798+ eventBuilders . deletedPrice ( {
799+ data : pricesToDelete . map ( ( id ) => ( { id } ) ) ,
800+ sharedContext,
801+ } )
802+ }
803+
804+ const priceSets = await this . priceSetService_ . list (
805+ { id : normalizedData . map ( ( { id } ) => id ) } ,
806+ {
807+ relations : [ "prices" , "prices.price_rules" ] ,
808+ } ,
809+ sharedContext
810+ )
811+
812+ const upsertedPricesMap = new Map < string , InferEntityType < typeof Price > [ ] > ( )
813+
814+ const upsertedPrices = [ ...createdPrices , ...updatedPrices ]
815+ upsertedPrices . forEach ( ( price ) => {
816+ upsertedPricesMap . set ( price . price_set_id , [
817+ ...( upsertedPricesMap . get ( price . price_set_id ) || [ ] ) ,
818+ price ,
819+ ] )
682820 } )
683821
684- return priceSets . map ( ( ps ) => {
685- if ( ps . prices ) {
686- ps . prices = ( ps . prices as any ) . filter ( ( p ) => ! p . price_list_id )
687- }
822+ // re assign the prices to the price sets to not have to refetch after the transaction and keep the bahaviour the same as expected. If the user needs more data, he can still re list the price set with the expected fields and relations that he needs
823+
824+ priceSets . forEach ( ( ps ) => {
825+ ps . prices = upsertedPricesMap . get ( ps . id ) || [ ]
826+ } )
688827
689- return ps
828+ eventBuilders . updatedPriceSet ( {
829+ data : priceSets . map ( ( ps ) => ( { id : ps . id } ) ) ,
830+ sharedContext,
690831 } )
832+
833+ return priceSets
691834 }
692835
693836 private async normalizeUpdateData ( data : ServiceTypes . UpdatePriceSetInput [ ] ) {
0 commit comments