USP_CREDIT_ADDORDERREFUND
Parameters
Parameter | Parameter Type | Mode | Description |
---|---|---|---|
@ID | uniqueidentifier | INOUT | |
@CHANGEAGENTID | uniqueidentifier | IN | |
@ORDERID | uniqueidentifier | IN | |
@ITEMS | xml | IN | |
@TAXES | xml | IN | |
@REFUNDMETHODS | xml | IN | |
@COMMENT | nvarchar(500) | IN | |
@CREDITREASONCODEID | uniqueidentifier | IN | |
@CURRENTAPPUSERID | uniqueidentifier | IN | |
@TOTAL | money | IN | |
@TIMESTAMP | bigint | IN | |
@ADDRESSID | uniqueidentifier | IN |
Definition
Copy
CREATE procedure dbo.USP_CREDIT_ADDORDERREFUND
(
@ID uniqueidentifier = null output,
@CHANGEAGENTID uniqueidentifier = null,
@ORDERID uniqueidentifier,
@ITEMS xml = null,
@TAXES xml = null,
@REFUNDMETHODS xml = null,
@COMMENT nvarchar(500) = '',
@CREDITREASONCODEID uniqueidentifier = null,
@CURRENTAPPUSERID uniqueidentifier = null,
@TOTAL money = null, -- OBSOLETE - Maximum refundable amount (i.e. total value of the order including discounts).
@TIMESTAMP bigint = null,
@ADDRESSID uniqueidentifier = null
)
as
begin
set nocount on;
if @ID is null
set @ID = newid();
if @CHANGEAGENTID is null
exec dbo.USP_CHANGEAGENT_GETORCREATECHANGEAGENT @CHANGEAGENTID output;
declare @CURRENTDATE datetime = getdate();
declare @POSTSTATUSCODE tinyint;
declare @ORDERREVENUEID uniqueidentifier;
--Make sure nobody else has done a refund since the form was opened (coalescing the TSLONG in case of null does nothing. x <> null is never true).
if @TIMESTAMP <> (select TSLONG from dbo.SALESORDER where ID = @ORDERID) begin
if @ORDERID is null or @ORDERID = '00000000-0000-0000-0000-000000000000'
raiserror('ERR_TRANSACTIONID_ORDERNOTSELECTED', 13, 1);
else
raiserror('ERR_CREDITITEMS_CONFLICTINGREFUND', 13, 1);
return 1;
end
else begin
update dbo.SALESORDER
set DATECHANGED = @CURRENTDATE, CHANGEDBYID = @CHANGEAGENTID
where ID = @ORDERID;
select @ORDERREVENUEID = FT.ID, @POSTSTATUSCODE = FT.POSTSTATUSCODE
from dbo.SALESORDER SO
inner join dbo.FINANCIALTRANSACTION FT on FT.ID = SO.REVENUEID
where SO.ID = @ORDERID;
end
-- Get refund items from input parameter @ITEMS
declare @CREDITITEMS table(
ID uniqueidentifier,
SALESORDERITEMID uniqueidentifier,
PRICE money,
ITEMDISCOUNTS money, -- Item-level discounts being reversed for a particular item (not included in the @ITEMS xml).
ORDERDISCOUNTS money, -- Order-level discounts being reversed for a particular item (not included in the @ITEMS xml).
AMOUNTPAID money,
AMOUNTTOREFUND money, -- This will be the BASEAMOUNT on the refund line item.
TYPECODE tinyint,
[DESCRIPTION] nvarchar(255),
EVENTREGISTRANTID uniqueidentifier,
ITEMID uniqueidentifier, -- membership transaction ID, membership addon ID, or itemized item ID (ticket/merch unit/item-level fee)
REVENUESPLITID uniqueidentifier,
CONTRIBUTEDREVENUE money,
WILLBECANCELLED bit
);
insert into @CREDITITEMS
select
newid(),
T.item.value('(@SALESORDERITEMID)[1]','uniqueidentifier') SALESORDERITEMID,
T.item.value('(@PRICE)[1]','money') PRICE,
0 ITEMDISCOUNTS,
0 ORDERDISCOUNTS,
T.item.value('(@AMOUNTPAID)[1]','money') AMOUNTPAID,
T.item.value('(@AMOUNTTOREFUND)[1]','money') AMOUNTTOREFUND,
T.item.value('(@TYPECODE)[1]','tinyint') TYPECODE,
(select [DESCRIPTION] from dbo.SALESORDERITEM where ID = T.item.value('(@SALESORDERITEMID)[1]','uniqueidentifier')) [DESCRIPTION],
case
when T.item.value('(@EVENTREGISTRANTID)[1]','uniqueidentifier') = '00000000-0000-0000-0000-000000000000'
then null
else
T.item.value('(@EVENTREGISTRANTID)[1]','uniqueidentifier')
end EVENTREGISTRANTID,
case
when T.item.value('(@ITEMID)[1]','uniqueidentifier') = '00000000-0000-0000-0000-000000000000'
then null
else
T.item.value('(@ITEMID)[1]','uniqueidentifier')
end ITEMID,
case
when T.item.value('(@REVENUESPLITID)[1]','uniqueidentifier') = '00000000-0000-0000-0000-000000000000'
then null
else
T.item.value('(@REVENUESPLITID)[1]','uniqueidentifier')
end REVENUESPLITID,
0 CONTRIBUTEDREVENUE,
T.item.value('(@WILLBECANCELLED)[1]', 'bit') WILLBECANCELLED
from @ITEMS.nodes('/ITEMS/ITEM') T(item)
where T.item.value('(@INCLUDE)[1]','bit') = 1;
declare @TAXTABLE table (
TAXID uniqueidentifier, -- actually refers to SALESORDERITEM, not TAX.
[DESCRIPTION] nvarchar(200),
REVENUESPLITID uniqueidentifier,
CURRENTTAX money -- amount of tax to refund
);
insert into @TAXTABLE
select
T.item.value('(@TAXID)[1]','uniqueidentifier'),
(select [DESCRIPTION] from dbo.SALESORDERITEM where ID = T.item.value('(@TAXID)[1]','uniqueidentifier')),
T.item.value('(@REVENUESPLITID)[1]','uniqueidentifier'),
round(T.item.value('(@CURRENTTAX)[1]','money'), 2)
from @TAXES.nodes('/TAXES/ITEM') T(item)
where T.item.value('(@CURRENTTAX)[1]','money') > 0;
-- As of 2013/09/20, we have decided that order-level discounts will be reversed only if everything they apply to is fully refunded.
-- In other words, if there is a discounted item that is *not* fully refunded, then we *do not* reverse order discounts.
declare @SHOULDREVERSEORDERDISCOUNTS bit = case
when (
-- the number of discounted ticket and merch items that are being fully refunded in this transaction
select count(ID)
from @CREDITITEMS CREDITITEMS
where TYPECODE in (0,14) -- Only tickets and merch can have order-level discounts (although merch might not)
and AMOUNTPAID < PRICE -- Ticket or merch item was discounted => order-level discount was applicable (this excludes non-discountable merch)
and AMOUNTTOREFUND = AMOUNTPAID -- fully refunding the item (because if any like this exist, we do NOT reverse the order-level discount)
) = (
-- the number of discounted ticket and merch items on the entire order
select count(TICKET.ID) + count(MERCH.ID)
from dbo.SALESORDERITEM SOI
left join dbo.TICKET on TICKET.SALESORDERITEMTICKETID = SOI.ID
left join dbo.SALESORDERITEMMERCHANDISEUNIT MERCH on MERCH.SALESORDERITEMMERCHANDISEID = SOI.ID
where SOI.SALESORDERID = @ORDERID
and (
coalesce(TICKET.ORDERLEVELDISCOUNTSAPPLIED, MERCH.ORDERLEVELDISCOUNTSAPPLIED) > 0
or coalesce(TICKET.ITEMLEVELDISCOUNTSAPPLIED, MERCH.ITEMLEVELDISCOUNTSAPPLIED) > 0
)
) then 1
else 0
end;
-- Determine which item-level and order-level discounts will be reversed (only if the full amount paid for the ticket/merch unit is being refunded).
update @CREDITITEMS
set
ITEMDISCOUNTS = TICKET.ITEMLEVELDISCOUNTSAPPLIED,
ORDERDISCOUNTS = case when @SHOULDREVERSEORDERDISCOUNTS = 1 then TICKET.ORDERLEVELDISCOUNTSAPPLIED else 0 end
from @CREDITITEMS CREDITITEMS
inner join dbo.TICKET on TICKET.ID = CREDITITEMS.ITEMID
where TICKET.AMOUNTPAID = CREDITITEMS.AMOUNTTOREFUND;
update @CREDITITEMS
set
ITEMDISCOUNTS = MERCHANDISE.ITEMLEVELDISCOUNTSAPPLIED,
ORDERDISCOUNTS = case when @SHOULDREVERSEORDERDISCOUNTS = 1 then MERCHANDISE.ORDERLEVELDISCOUNTSAPPLIED else 0 end
from @CREDITITEMS CREDITITEMS
inner join dbo.SALESORDERITEMMERCHANDISEUNIT MERCHANDISE on MERCHANDISE.ID = CREDITITEMS.ITEMID
where MERCHANDISE.AMOUNTPAID = CREDITITEMS.AMOUNTTOREFUND;
-- This must be a sum, because tickets may be applied along with a normal promotion.
update CREDITITEMS
set
ITEMDISCOUNTS = coalesce(PROMOS.AMOUNT, 0)
from @CREDITITEMS CREDITITEMS
inner join dbo.SALESORDERITEM MEMBERSHIPITEM on MEMBERSHIPITEM.ID = CREDITITEMS.SALESORDERITEMID
cross apply (
select sum(AMOUNT) AMOUNT
from dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTION
where SALESORDERITEMID = MEMBERSHIPITEM.ID
) PROMOS
where MEMBERSHIPITEM.PRICE = CREDITITEMS.AMOUNTTOREFUND + PROMOS.AMOUNT;
/******** Validate refund items and refund payments *******/
declare @REFUNDEDITEMTOTAL money = (
(
select sum(case when TYPECODE = 5 then 0 else AMOUNTTOREFUND end)
from @CREDITITEMS
)
+ coalesce((select sum(CURRENTTAX) from @TAXTABLE),0)
);
-- Error if no items were selected
if not exists (select 1 from @CREDITITEMS)
begin
raiserror('ERR_ITEMS_NONESELECTED', 13, 1);
return 1;
end
-- Error if the quantity refunding for any item is greater than the quantity available
-- -- (original quantity - already refunded quantity - quantity we're refunding right now)
if exists(
select 1
from (
select SALESORDERITEMID, count(ID) NUMREFUNDING
from @CREDITITEMS
where
not TYPECODE in (0,3,14) -- tickets, item fees, and merch items each have their own ITEMID, which will be validated roughly 30 lines below
or (TYPECODE = 3 and ITEMID is null)
group by SALESORDERITEMID
) CREDITITEMS_GROUPED
inner join dbo.SALESORDERITEM on SALESORDERITEM.ID = CREDITITEMS_GROUPED.SALESORDERITEMID
left join dbo.CREDITITEM_EXT on CREDITITEM_EXT.SALESORDERITEMID = SALESORDERITEM.ID
left join dbo.FINANCIALTRANSACTIONLINEITEM FTLI on FTLI.ID = CREDITITEM_EXT.ID
group by SALESORDERITEM.ID, SALESORDERITEM.QUANTITY, CREDITITEMS_GROUPED.NUMREFUNDING
having SALESORDERITEM.QUANTITY - coalesce(sum(FTLI.QUANTITY),0) - CREDITITEMS_GROUPED.NUMREFUNDING < 0
) begin
raiserror('ERR_ITEMS_EXCEEDSQUANTITYAVAILABLE', 13, 1);
return 1;
end
-- Error if the amount refunding is less than zero for any of the items
if exists (select 1 from @CREDITITEMS where AMOUNTTOREFUND < 0)
begin
raiserror('ERR_ITEMS_NEGATIVEAMOUNT', 13, 1);
return 1;
end
-- Error if the payments do not add up to the refund total
if coalesce((select sum(T.item.value('(@AMOUNTREFUNDING)[1]','money')) from @REFUNDMETHODS.nodes('/REFUNDMETHODS/ITEM') T(item)),0) <> @REFUNDEDITEMTOTAL
begin
raiserror('ERR_REFUNDMETHODS_DIFFERENTFROMTOTALAMOUNT', 13, 1);
return 1;
end
-- Error if the refund amount is greater than the total payment amount remaining
if @REFUNDEDITEMTOTAL > (
select sum(PAYMENTAMOUNTREMAINING)
from (
select
FINANCIALTRANSACTION.BASEAMOUNT - coalesce((
select sum(coalesce(CREDITPAYMENT.AMOUNT, 0)) from dbo.CREDITPAYMENT where REVENUEID = FINANCIALTRANSACTION.ID
),0) as PAYMENTAMOUNTREMAINING -- (original amount - refunded amount)
from dbo.SALESORDER
inner join dbo.SALESORDERPAYMENT on SALESORDERPAYMENT.SALESORDERID = SALESORDER.ID
inner join dbo.FINANCIALTRANSACTION on SALESORDERPAYMENT.PAYMENTID = FINANCIALTRANSACTION.ID
where SALESORDER.ID = @ORDERID
) PAYMENTS
)
begin
raiserror('ERR_REFUNDAMOUNT_EXCEEDSLIMIT', 13, 1);
return 1;
end
-- Error if an item has already been refunded
if exists (
select 1 from @CREDITITEMS ITEMS
inner join dbo.CREDITITEM_EXT on ITEMS.ITEMID = CREDITITEM_EXT.SALESORDERITEMIZEDITEMID
inner join dbo.FINANCIALTRANSACTIONLINEITEM FTLI on FTLI.ID = CREDITITEM_EXT.ID
where FTLI.BASEAMOUNT > 0
)
begin
raiserror('ERR_ITEMALREADYREFUNDED', 13, 1);
return 1;
end
-- Error if an itemized item or membership item has no ITEMID
if exists (
select 1
from @CREDITITEMS ITEMS
where TYPECODE in (0,1,14,16) -- ticket, membership, merch, add-on
and ITEMID is null
union all
-- item-level fees
select 1
from @CREDITITEMS ITEMS
inner join dbo.SALESORDERITEMFEE FEEITEM on FEEITEM.ID = ITEMS.SALESORDERITEMID
where FEEITEM.APPLIESTOCODE = 1 -- item-level
and ITEMS.ITEMID is null
)
begin
raiserror('ERR_ITEMWITHOUTITEMID', 13, 1);
return 1;
end
-- Error if there are any later membership transactions
if exists (
select 1 from @CREDITITEMS MEMBERSHIPITEM
inner join dbo.MEMBERSHIPTRANSACTION REFUNDINGTRANSACTION on REFUNDINGTRANSACTION.ID = MEMBERSHIPITEM.ITEMID
outer apply (
select top 1 ID
from dbo.MEMBERSHIPTRANSACTION
where MEMBERSHIPID = REFUNDINGTRANSACTION.MEMBERSHIPID
order by DATEADDED desc
) LATESTTRANSACTION
where MEMBERSHIPITEM.ITEMID <> LATESTTRANSACTION.ID
)
begin
raiserror('ERR_ITEMS_MEMBERSHIPREFUND_HASNEWERMEMBERSHIPTRANSACTIONS', 13, 1);
return 1;
end
-- Error if there are memberships with contributed revenue that are not being fully refunded
if exists (
select 1 from @CREDITITEMS MEMBERSHIPITEM
inner join dbo.MEMBERSHIPTRANSACTION REFUNDINGTRANSACTION on REFUNDINGTRANSACTION.ID = MEMBERSHIPITEM.ITEMID
inner join dbo.FINANCIALTRANSACTIONLINEITEM MEMBERSHIPLI on MEMBERSHIPLI.ID = REFUNDINGTRANSACTION.REVENUESPLITID
inner join dbo.FINANCIALTRANSACTIONLINEITEM CONTRIBUTEDLI on CONTRIBUTEDLI.SOURCELINEITEMID = MEMBERSHIPLI.ID
inner join dbo.REVENUESPLIT_EXT EXT
on (EXT.ID = CONTRIBUTEDLI.ID and EXT.TYPECODE = 0 and EXT.APPLICATIONCODE = 0 and CONTRIBUTEDLI.TYPECODE = 0) -- Standard donation line item whose source is the membership line item
group by MEMBERSHIPLI.ID, MEMBERSHIPLI.BASEAMOUNT, MEMBERSHIPITEM.AMOUNTTOREFUND, MEMBERSHIPITEM.ITEMDISCOUNTS
having MEMBERSHIPLI.BASEAMOUNT + sum(CONTRIBUTEDLI.BASEAMOUNT) <> MEMBERSHIPITEM.AMOUNTTOREFUND + MEMBERSHIPITEM.ITEMDISCOUNTS
)
begin
raiserror('ERR_ITEMS_MEMBERSHIPPARTIALREFUND_HASCONTRIBUTEDREVENUE', 13, 1);
return 1;
end
-- If there are memberships with add-ons that are also on this transaction and the addons are not all being refunded
if exists (
select 1 from @CREDITITEMS MEMBERSHIPITEM
inner join dbo.MEMBERSHIPTRANSACTION REFUNDINGTRANSACTION on REFUNDINGTRANSACTION.ID = MEMBERSHIPITEM.ITEMID
inner join dbo.MEMBERSHIPADDON ADDONSINTRANSACTION on ADDONSINTRANSACTION.MEMBERSHIPTRANSACTIONID = REFUNDINGTRANSACTION.ID
where
MEMBERSHIPITEM.WILLBECANCELLED = 1
and (select count(*) from @CREDITITEMS where ITEMID = ADDONSINTRANSACTION.ID) <> (ADDONSINTRANSACTION.QUANTITY - ADDONSINTRANSACTION.NUMCANCELLED)
)
begin
raiserror('BBERR_ITEMS_MEMBERSHIPCANCEL_NOTREFUNDINGADDONS', 13, 1);
return 1;
end
-- Reduce or delete recognition credits for refunded donations.
if exists
(
select 1
from @CREDITITEMS ITEMS
where ITEMS.TYPECODE = 2 -- Donation
)
begin
-- Recognition credits have no significant financial implications, so we'll just cut them by the same percentage as the donation.
declare @DONATIONREFUNDS table (REVENUESPLITID uniqueidentifier, PERCENTREFUNDED real);
insert into @DONATIONREFUNDS
select
SALESORDERITEMDONATION.REVENUESPLITID,
case
when SALESORDERITEMDONATION.AMOUNT = 0.0 or CREDITITEMS.AMOUNTTOREFUND = SALESORDERITEMDONATION.AMOUNT
then 1.0
else CREDITITEMS.AMOUNTTOREFUND / SALESORDERITEMDONATION.AMOUNT
end
from dbo.SALESORDERITEMDONATION
inner join @CREDITITEMS CREDITITEMS on CREDITITEMS.SALESORDERITEMID = SALESORDERITEMDONATION.ID
where CREDITITEMS.TYPECODE = 2;
-- Delete if we're refunding the entire donation
delete from dbo.REVENUERECOGNITION
where REVENUERECOGNITION.REVENUESPLITID in
(
select ITEMS.REVENUESPLITID
from @DONATIONREFUNDS ITEMS
where PERCENTREFUNDED = 1.0
);
-- Reduce if we're only refunding part of the donation.
update dbo.REVENUERECOGNITION
set AMOUNT = AMOUNT * (1.0 - DONATIONREFUNDS.PERCENTREFUNDED)
from dbo.REVENUERECOGNITION
inner join @DONATIONREFUNDS DONATIONREFUNDS on REVENUERECOGNITION.REVENUESPLITID = DONATIONREFUNDS.REVENUESPLITID
where DONATIONREFUNDS.PERCENTREFUNDED <> 1.0;
end
-- Find contributed revenue:
-- 1. Find the contributory line items (donation line items whose sourcelineitemid is the revenuesplitid of an item we're trying to refund).
-- 2. For discounted memberships, prorate the discount across the contributory line items and subtract it from the membership line item.
-- 3. Subtract the contributory amount from the amount to refund of the membership or event registration item.
declare @CONTRIBUTEDREVENUE table (
CONTRIBUTEDLINEITEMID uniqueidentifier,
SOURCELINEITEMID uniqueidentifier,
TYPECODE tinyint,
[DESCRIPTION] nvarchar(200),
ORIGINALAMOUNT money,
DISCOUNTS money
);
-- Find contributed revenue (for memberships and event registrations).
insert into @CONTRIBUTEDREVENUE
select
CONTRIBUTEDLI.ID CONTRIBUTEDLINEITEMID,
CONTRIBUTEDLI.SOURCELINEITEMID,
case
when CONTRIBUTEDLI_EXT.APPLICATIONCODE = 1 then 6
else 2
end TYPECODE,
CONTRIBUTEDLI.[DESCRIPTION],
CONTRIBUTEDLI.BASEAMOUNT ORIGINALAMOUNT,
null DISCOUNTS
from @CREDITITEMS CREDITITEMS
inner join dbo.FINANCIALTRANSACTIONLINEITEM CONTRIBUTEDLI on CONTRIBUTEDLI.SOURCELINEITEMID = CREDITITEMS.REVENUESPLITID
inner join dbo.REVENUESPLIT_EXT CONTRIBUTEDLI_EXT on CONTRIBUTEDLI_EXT.ID = CONTRIBUTEDLI.ID
where
CONTRIBUTEDLI_EXT.TYPECODE = 0 -- Gift
and CONTRIBUTEDLI_EXT.APPLICATIONCODE in (0,1) -- Application type is "Donation" or "Event Registration"
and CONTRIBUTEDLI.TYPECODE = 0; -- Standard
if @@ROWCOUNT > 0
begin
-- Set the contributed amount on items with partially contributed revenue.
update @CREDITITEMS
set CONTRIBUTEDREVENUE = coalesce((select sum(ORIGINALAMOUNT) from @CONTRIBUTEDREVENUE where SOURCELINEITEMID = CI.REVENUESPLITID),0)
from @CREDITITEMS CI;
-- Prorate item-level discounts across contributed items.
-- This could be more efficient (when discount is more than contributed amount, no proration is necessary, because the full contributory amount is discounted), but that's not important.
update @CONTRIBUTEDREVENUE
set DISCOUNTS = PRORATEDAMOUNTS.AMOUNT
from @CREDITITEMS CREDITITEMS
cross apply dbo.UFN_SPLITS_PRORATEAMOUNTS(
CREDITITEMS.CONTRIBUTEDREVENUE,
(select case when CREDITITEMS.ITEMDISCOUNTS > CREDITITEMS.CONTRIBUTEDREVENUE then CREDITITEMS.CONTRIBUTEDREVENUE else CREDITITEMS.ITEMDISCOUNTS end),
2,
(
select CONTRIBUTEDREVENUE.CONTRIBUTEDLINEITEMID ID, CONTRIBUTEDREVENUE.ORIGINALAMOUNT AMOUNT
from @CONTRIBUTEDREVENUE CONTRIBUTEDREVENUE
where CONTRIBUTEDREVENUE.SOURCELINEITEMID = CREDITITEMS.REVENUESPLITID
for xml raw('ITEM'),type,elements,root('AMOUNTSTOPRORATE'),binary base64
)
) PRORATEDAMOUNTS
where PRORATEDAMOUNTS.ID = CONTRIBUTEDLINEITEMID;
-- Reduce the discount amount for partially contributed items (since the discounts have been moved to the contributory line items).
-- Also reduce the refund amount to account for discounts in excess of the contributory revenue.
update @CREDITITEMS
set
AMOUNTTOREFUND = case when CONTRIBUTEDREVENUE > ITEMDISCOUNTS then PRICE - CONTRIBUTEDREVENUE else AMOUNTTOREFUND end,
ITEMDISCOUNTS = case when CONTRIBUTEDREVENUE > ITEMDISCOUNTS then 0 else ITEMDISCOUNTS - CONTRIBUTEDREVENUE end
where CONTRIBUTEDREVENUE > 0;
-- At this point:
-- -- 1. The ITEMDISCOUNTS of a partially contributed credit item will be max(0, discounted amount - contributed amount)
-- -- 2. The AMOUNTTOREFUND of a partially contributed credit item will be PRICE - max(contributed amount, discounted amount).
-- Otherwise the refund GL will not be generated correctly.
end
declare @INS table
(
ID uniqueidentifier,
CREDITID uniqueidentifier,
SALESORDERITEMID uniqueidentifier,
PRICE money,
DISCOUNTS money,
TYPECODE tinyint,
[DESCRIPTION] nvarchar(700),
GROUPID uniqueidentifier,
GROUPTYPECODE tinyint,
SOURCELINEITEMID uniqueidentifier,
REFUNDAMOUNT money,
UNITVALUE money,
ITEMID uniqueidentifier
);
-- This may not be the most intuitive data set-up in the world (particularly the roles that UNITVALUE and DISCOUNTS play in GL creation).
-- However, the alternatives are breaking backward-compatibility of refund data or converting all existing refunds into something that makes more sense,
-- neither of which we have time to do right now.
insert into @INS
select
ID,
@ID,
SALESORDERITEMID,
PRICE,
ITEMDISCOUNTS,
TYPECODE,
[DESCRIPTION],
EVENTREGISTRANTID,
case when TYPECODE = 6 then 1 else 0 end GROUPTYPECODE,
REVENUESPLITID,
AMOUNTTOREFUND + ORDERDISCOUNTS, -- Refund amount needs to include order discounts (reversed in separate line item)
AMOUNTTOREFUND + CONTRIBUTEDREVENUE + ITEMDISCOUNTS + ORDERDISCOUNTS, -- Unit value needs to include discounts and contributed revenue (for GL generation)
ITEMID
from @CREDITITEMS
union all
--taxes
select
newid(),
@ID,
TAXES.TAXID,
TAXES.CURRENTTAX PRICE,
0 DISCOUNTS,
4 TYPECODE,
(select [DESCRIPTION] from dbo.SALESORDERITEM where ID = TAXES.TAXID),
null GROUPID,
0 GROUPTYPECODE,
TAXES.REVENUESPLITID,
TAXES.CURRENTTAX,
TAXES.CURRENTTAX UNITVALUE,
null ITEMID
from @TAXTABLE TAXES
union all
-- Contributory portions of memberships and event registrations
select
newid(),
@ID,
null SALESORDERITEMID,
ORIGINALAMOUNT PRICE,
DISCOUNTS,
TYPECODE,
[DESCRIPTION],
null GROUPID,
0 GROUPTYPECODE,
CONTRIBUTEDLINEITEMID,
ORIGINALAMOUNT - DISCOUNTS REFUNDAMOUNT,
ORIGINALAMOUNT UNITVALUE,
null ITEMID
from @CONTRIBUTEDREVENUE
where ORIGINALAMOUNT - DISCOUNTS <> 0;
if @SHOULDREVERSEORDERDISCOUNTS = 1
begin
insert into @INS
select
newid() as ID,
@ID as CREDITID,
SALESORDERITEM.ID as SALESORDERITEMID,
-DISCOUNTLI.BASEAMOUNT as PRICE,
0 DISCOUNTS,
5 TYPECODE,
SALESORDERITEM.[DESCRIPTION],
null GROUPID,
0 GROUPTYPECODE,
DISCOUNTLI.ID as SOURCELINEITEMID, -- Formerly the source ID here was always null. Having it makes GL a lot easier, though.
-DISCOUNTLI.BASEAMOUNT as REFUNDAMOUNT,
-DISCOUNTLI.BASEAMOUNT as UNITVALUE,
null ITEMID
from dbo.SALESORDERITEM
inner join dbo.SALESORDERITEMORDERDISCOUNT ORDERDISCOUNT on ORDERDISCOUNT.ID = SALESORDERITEM.ID
inner join dbo.CREDITITEM_EXT DISCOUNTLI_EXT on (DISCOUNTLI_EXT.DISCOUNTID = ORDERDISCOUNT.DISCOUNTID or (DISCOUNTLI_EXT.DISCOUNTID is null and ORDERDISCOUNT.ISADJUSTABLEDISCOUNT = 1))
inner join dbo.FINANCIALTRANSACTIONLINEITEM DISCOUNTLI on DISCOUNTLI.ID = DISCOUNTLI_EXT.ID and DISCOUNTLI.FINANCIALTRANSACTIONID = @ORDERREVENUEID
where SALESORDERITEM.TYPECODE = 5
and SALESORDERITEM.SALESORDERID = @ORDERID;
end
begin try
-- Bug #167166 - MDC
-- Weird that the @TRANSACTIONDATE is being inserted into a datetimeoffset column. Always has offset 0.
declare @TRANSACTIONDATE datetime = dbo.UFN_TIMEZONEENTRY_GETSYSTEMDEFAULTDATEFROMUTC(getutcdate());
declare @CURRENCYID uniqueidentifier = (select ID from dbo.CURRENCY where ISORGANIZATIONCURRENCY = 1);
declare @PDACCOUNTSYSTEMID uniqueidentifier;
declare @ALLOWGLDISTRIBUTIONS bit;
select
@PDACCOUNTSYSTEMID = ID,
@ALLOWGLDISTRIBUTIONS = ALLOWGLDISTRIBUTIONS
from
dbo.UFN_PDACCOUNTSYSTEM_DEFAULTORSYSTEM();
declare @CONSTITUENTID uniqueidentifier = (select CONSTITUENTID from dbo.SALESORDER where ID = @ORDERID);
insert into dbo.FINANCIALTRANSACTION
(
ID,
TRANSACTIONAMOUNT,
BASEAMOUNT,
ORGAMOUNT,
TRANSACTIONCURRENCYID,
TYPECODE,
PDACCOUNTSYSTEMID,
[DESCRIPTION],
[DATE],
POSTDATE,
POSTSTATUSCODE,
PARENTID,
APPUSERID,
CONSTITUENTID,
DATEADDED, DATECHANGED, ADDEDBYID, CHANGEDBYID
)
values
(
@ID,
@REFUNDEDITEMTOTAL,
@REFUNDEDITEMTOTAL,
@REFUNDEDITEMTOTAL,
@CURRENCYID,
23, --Refund
@PDACCOUNTSYSTEMID,
@COMMENT,
@TRANSACTIONDATE,
case when @ALLOWGLDISTRIBUTIONS = 1 and isnull(@POSTSTATUSCODE, 1) != 3 then cast(@TRANSACTIONDATE as date) else null end,
case when @ALLOWGLDISTRIBUTIONS = 1 and isnull(@POSTSTATUSCODE, 1) != 3 then 1 else 3 end,
@ORDERREVENUEID,
@CURRENTAPPUSERID,
@CONSTITUENTID,
@CURRENTDATE, @CURRENTDATE, @CHANGEAGENTID, @CHANGEAGENTID
);
insert into dbo.CREDIT_EXT
(
ID,
SALESORDERID,
CREDITREASONCODEID,
DATEADDED, DATECHANGED, ADDEDBYID, CHANGEDBYID
)
values
(
@ID,
@ORDERID,
case @CREDITREASONCODEID
when '00000000-0000-0000-0000-000000000000' then null
else @CREDITREASONCODEID
end,
@CURRENTDATE, @CURRENTDATE, @CHANGEAGENTID, @CHANGEAGENTID
);
exec dbo.USP_CREDIT_ADDCREDITPAYMENTS @ID, @REFUNDMETHODS, @CHANGEAGENTID, @CURRENTAPPUSERID, @ADDRESSID;
insert into dbo.FINANCIALTRANSACTIONLINEITEM
(
ID,
FINANCIALTRANSACTIONID,
UNITVALUE,
QUANTITY,
[DESCRIPTION],
TRANSACTIONAMOUNT,
BASEAMOUNT,
ORGAMOUNT,
POSTDATE,
POSTSTATUSCODE,
TYPECODE,
SOURCELINEITEMID,
ADDEDBYID, CHANGEDBYID, DATEADDED, DATECHANGED
)
select
I.ID,
I.CREDITID,
I.UNITVALUE,
1 QUANTITY,
I.[DESCRIPTION],
I.REFUNDAMOUNT,
I.REFUNDAMOUNT,
I.REFUNDAMOUNT,
FT.POSTDATE,
FT.POSTSTATUSCODE,
case I.TYPECODE
when 3 then 7 --Fee
when 4 then 8 --Tax
when 5 then 5 --Discount
else 0
end,
I.SOURCELINEITEMID,
@CHANGEAGENTID, @CHANGEAGENTID, @CURRENTDATE, @CURRENTDATE
from @INS I
inner join dbo.FINANCIALTRANSACTION FT on FT.ID = I.CREDITID;
insert into dbo.CREDITITEM_EXT
(
ID,
CREDITID,
DISCOUNTS,
FEES,
GROUPID,
GROUPTYPECODE,
SALESORDERITEMID,
TYPECODE,
SALESORDERITEMIZEDITEMID,
ADDEDBYID, CHANGEDBYID, DATEADDED, DATECHANGED
)
select
ID,
CREDITID,
DISCOUNTS,
0 FEES,
GROUPID,
GROUPTYPECODE,
SALESORDERITEMID,
TYPECODE,
case when TYPECODE in (1,16) then null else ITEMID end, -- Membership and Add-on are not itemized
@CHANGEAGENTID, @CHANGEAGENTID, @CURRENTDATE, @CURRENTDATE
from @INS;
-- Update ISREFUNDED for refunded tickets.
-- Whether the ticket is cancelled as a result of this refund is handled elsewhere (likely near RefundAddViewDataFormUIModel).
update dbo.TICKET
set ISREFUNDED = 1
where ID in (select ITEMID from @CREDITITEMS where TYPECODE = 0);
-- Update ticket applied to membership fields for tickets that were applied to a fully refunded membership.
update dbo.TICKET
set
TICKET.APPLIEDTOMEMBERSHIP = 0,
TICKET.APPLIEDTOMEMBERSHIPSALESORDERID = null
from dbo.TICKET
inner join dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTIONAPPLIEDITEMTICKET APPLIEDTICKETS on TICKET.ID = APPLIEDTICKETS.TICKETID
inner join dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTIONAPPLIEDITEM APPLIEDITEM on APPLIEDTICKETS.SALESORDERITEMMEMBERSHIPITEMPROMOTIONAPPLIEDITEMID = APPLIEDITEM.ID
inner join dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTION PROMOTION on APPLIEDITEM.SALESORDERITEMMEMBERSHIPITEMPROMOTIONID = PROMOTION.ID
inner join @CREDITITEMS MEMBERSHIPCREDITITEMS on PROMOTION.SALESORDERITEMID = MEMBERSHIPCREDITITEMS.SALESORDERITEMID
where MEMBERSHIPCREDITITEMS.AMOUNTTOREFUND = MEMBERSHIPCREDITITEMS.AMOUNTPAID
or MEMBERSHIPCREDITITEMS.CONTRIBUTEDREVENUE > 0; -- hacky; memberships with contributed revenue must be fully refunded, so we know to unmark the ticket as applied.
-- Update refunded amount for refunded merchandise item (whether the item is returned is determined by the user after the refund is complete).
update dbo.SALESORDERITEMMERCHANDISEUNIT
set REFUNDEDAMOUNT = coalesce(REFUNDEDAMOUNT,0) + ITEMS.AMOUNTTOREFUND
from dbo.SALESORDERITEMMERCHANDISEUNIT
inner join @CREDITITEMS ITEMS on ITEMS.ITEMID = SALESORDERITEMMERCHANDISEUNIT.ID;
-- Update refunded amount for refunded ticket fees
update dbo.SALESORDERITEMTICKETFEE
set REFUNDEDAMOUNT = coalesce(REFUNDEDAMOUNT,0) + ITEMS.AMOUNTTOREFUND
from dbo.SALESORDERITEMTICKETFEE
inner join @CREDITITEMS ITEMS on ITEMS.ITEMID = SALESORDERITEMTICKETFEE.ID;
--Add credit membership items
insert into dbo.CREDITITEMMEMBERSHIP
(
ID,
MEMBERSHIPID,
MEMBERSHIPPROGRAMID,
MEMBERSHIPPROGRAMNAME,
MEMBERSHIPLEVELID,
MEMBERSHIPLEVELTERMID,
MEMBERSHIPLEVELTYPECODEID,
NUMBEROFCHILDREN,
COMMENTS,
ISGIFT,
GIVENBYID,
EXPIRATIONDATE,
MEMBERS,
ACTIONCODE,
ADDEDBYID,
CHANGEDBYID,
DATEADDED,
DATECHANGED,
WILLBECANCELLED
)
select
CREDITITEMS.ID,
SALESORDERITEMMEMBERSHIP.MEMBERSHIPID,
SALESORDERITEMMEMBERSHIP.MEMBERSHIPPROGRAMID,
SALESORDERITEMMEMBERSHIP.MEMBERSHIPPROGRAMNAME,
SALESORDERITEMMEMBERSHIP.MEMBERSHIPLEVELID,
SALESORDERITEMMEMBERSHIP.MEMBERSHIPLEVELTERMID,
SALESORDERITEMMEMBERSHIP.MEMBERSHIPLEVELTYPECODEID,
SALESORDERITEMMEMBERSHIP.NUMBEROFCHILDREN,
SALESORDERITEMMEMBERSHIP.COMMENTS,
SALESORDERITEMMEMBERSHIP.ISGIFT,
SALESORDERITEMMEMBERSHIP.GIVENBYID,
SALESORDERITEMMEMBERSHIP.EXPIRATIONDATE,
(
select dbo.UFN_CONSTITUENT_BUILDNAME(SOIM.CONSTITUENTID) MEMBERNAME
from dbo.SALESORDERITEMMEMBER as SOIM
inner join dbo.SALESORDERITEMMEMBERSHIP as SOIMS on SOIM.SALESORDERITEMMEMBERSHIPID = SOIMS.ID
inner join dbo.MEMBER on MEMBER.CONSTITUENTID = SOIM.CONSTITUENTID and MEMBER.MEMBERSHIPID = SOIMS.MEMBERSHIPID
where SALESORDERITEMMEMBERSHIP.ID = SOIM.SALESORDERITEMMEMBERSHIPID and MEMBER.ISDROPPED = 0
for xml raw('ITEM'),type,elements,root('MEMBERSHIPMEMBERS'),binary base64
),
coalesce(
MEMBERSHIPTRANSACTION.ACTIONCODE,
(select MEMBERSHIPTRANSACTION.ACTIONCODE from dbo.MEMBERSHIPTRANSACTION where ID = dbo.UFN_MEMBERSHIP_GETLASTMEMBERSHIPTRANSACTIONID(SALESORDERITEMMEMBERSHIP.MEMBERSHIPID))
),
@CHANGEAGENTID,
@CHANGEAGENTID,
@CURRENTDATE,
@CURRENTDATE,
CREDITITEMS.WILLBECANCELLED
from @CREDITITEMS CREDITITEMS
inner join dbo.SALESORDERITEM on CREDITITEMS.SALESORDERITEMID = SALESORDERITEM.ID
inner join dbo.SALESORDERITEMMEMBERSHIP on SALESORDERITEM.ID = SALESORDERITEMMEMBERSHIP.ID
left join dbo.MEMBERSHIPTRANSACTION on SALESORDERITEMMEMBERSHIP.MEMBERSHIPTRANSACTIONID = MEMBERSHIPTRANSACTION.ID
--Add credit event registration items
insert into dbo.CREDITITEMEVENTREGISTRATION
(
ID,
REGISTRANTID,
EVENTNAME,
ADDEDBYID,
CHANGEDBYID,
DATEADDED,
DATECHANGED
)
select
CREDITITEMS.ID,
SALESORDERITEMEVENTREGISTRATION.REGISTRANTID,
SALESORDERITEMEVENTREGISTRATION.EVENTNAME,
@CHANGEAGENTID,
@CHANGEAGENTID,
@CURRENTDATE,
@CURRENTDATE
from @CREDITITEMS as CREDITITEMS
inner join dbo.SALESORDERITEM on CREDITITEMS.SALESORDERITEMID = SALESORDERITEM.ID
inner join dbo.SALESORDERITEMEVENTREGISTRATION on SALESORDERITEM.ID = SALESORDERITEMEVENTREGISTRATION.ID
-- Add item level discounts (via the DISCOUNTS column on the CREDITITEM_EXT records we already created)
insert into dbo.CREDITITEMITEMDISCOUNT
(
CREDITITEMID,
SALESORDERITEMITEMDISCOUNTID,
AMOUNT,
ADDEDBYID,
CHANGEDBYID,
DATEADDED,
DATECHANGED
)
select distinct -- TODO: why distinct?
FTLI.ID,
SALESORDERITEMITEMDISCOUNT.ID,
EXT.DISCOUNTS,
@CHANGEAGENTID,
@CHANGEAGENTID,
@CURRENTDATE,
@CURRENTDATE
from dbo.SALESORDERITEMITEMDISCOUNT
inner join dbo.CREDITITEM_EXT as EXT on EXT.SALESORDERITEMID = SALESORDERITEMITEMDISCOUNT.SALESORDERITEMID
inner join dbo.FINANCIALTRANSACTIONLINEITEM as FTLI on FTLI.ID = EXT.ID
where FTLI.FINANCIALTRANSACTIONID = @ID
and EXT.DISCOUNTS > 0;
-- Add membership promotions (if the full amount paid for the membership is being returned)
insert into dbo.CREDITITEMMEMBERSHIPITEMPROMOTION
(
CREDITITEMID,
SALESORDERITEMMEMBERSHIPITEMPROMOTIONID,
AMOUNT,
ADDEDBYID,
CHANGEDBYID,
DATEADDED,
DATECHANGED
)
select distinct -- TODO: why distinct?
REFUNDEDMEMBERSHIPLI.ID,
SALESORDERITEMMEMBERSHIPITEMPROMOTION.ID,
SALESORDERITEMMEMBERSHIPITEMPROMOTION.AMOUNT, --Right now, the entire promotion has to be returned
@CHANGEAGENTID,
@CHANGEAGENTID,
@CURRENTDATE,
@CURRENTDATE
from dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTION
inner join dbo.CREDITITEM_EXT as EXT on EXT.SALESORDERITEMID = SALESORDERITEMMEMBERSHIPITEMPROMOTION.SALESORDERITEMID
inner join dbo.FINANCIALTRANSACTIONLINEITEM as REFUNDEDMEMBERSHIPLI on REFUNDEDMEMBERSHIPLI.ID = EXT.ID
where REFUNDEDMEMBERSHIPLI.FINANCIALTRANSACTIONID = @ID
and (
EXT.DISCOUNTS > 0 -- Unfortunately, this is 0 when the discount has moved to contributed revenue.
or exists ( -- Instead, link to the original thing we were trying to refund to determine whether we reversed discounts.
select 1
from @CONTRIBUTEDREVENUE CR
where CR.SOURCELINEITEMID = REFUNDEDMEMBERSHIPLI.SOURCELINEITEMID
and DISCOUNTS > 0
)
);
if @ALLOWGLDISTRIBUTIONS = 1 and isnull(@POSTSTATUSCODE, 1) != 3
begin
exec dbo.USP_REFUND_CREATEGLDISTRIBUTION @ID, @CHANGEAGENTID, @CURRENTDATE;
exec dbo.USP_REFUND_CREATEDISCOUNTGLDISTRIBUTION @ID, @CHANGEAGENTID, @CURRENTDATE;
end
-- (Updating membership transactions, members etc. for refunded memberships now occurs after the refund and depends on user interaction).
end try
begin catch
exec dbo.USP_RAISE_ERROR;
return 1;
end catch
return 0;
end