开发者

XSLT 1.0 help with recursion logic

开发者 https://www.devze.com 2022-12-27 06:42 出处:网络
I\'m having troubles with the logic and would apprecite any help/tips. I have <Deposits> elements and <Receipts> elements. However there isn\'t any identification what receipt was paid to

I'm having troubles with the logic and would apprecite any help/tips.

I have <Deposits> elements and <Receipts> elements. However there isn't any identification what receipt was paid toward what deposit.

I am trying to update the <Deposits> elements with the following attributes:

  • @DueAmont - the amount that is still due to pay
  • @Status - whether it's paid, outstanding (partly paid) or due
  • @ReceiptDate - the latest receipt's date that was paid towards this deposit

Every deposit could be paid with one or more receipts. It also could happen, that 1 receipt could cover one or more deposits. For example. If there are 3 deposits:

  1. 500
  2. 100
  3. 450

That are paid with the following receipts:

  1. 200
  2. 100
  3. 250

I want to get the following info:

Deposit 1 is fully paid (status=paid, dueAmount=0, receiptNum=3.

Deposit 2 is partly paid (status=outstanding, dueAmount=50, receiptNum=3.

Deposit 3 is not paid (status=due, dueAmount=450, receiptNum=NAN.

Actual XML:

 <Deposits DepositDate="2010-04-07T00:00:00" DepositTotalAmount="500.0000" NoOfPeople="10.0000" PerPerson="50.00"/>
 <Deposits DepositDate="2010-04-12T00:00:00" DepositTotalAmount="100.0000" NoOfPeople="10.0000" PerPerson="10.00"/>
 <Deposits DepositDate="2010-04-26T00:00:00" DepositTotalAmount="450.0000" NoOfPeople="10.0000" PerPerson="45.00"/>

<Receipts Amount="200.00" PaymentType="Cheque" Comment="" ReceiptAmount="200.00" ActionDate="2010-04-07T11:01:47" PayingInSlipNumber="" IsCard="No" IsRefund="No"/>
<Receipts Amount="100.00" PaymentType="Cheque" Comment="" ReceiptAmount="100.00" ActionDate="2010-04-11T11:01:47" PayingInSlipNumber="" IsCard="No" IsRefund="No"/>
<Receipts Amount="250.00" PaymentType="Cheque" Comment="" ReceiptAmount="250.00" ActionDate="2010-04-20T11:01:47" PayingInSlipNumber="" IsCard="No" IsRefund="No"/>

I've added comments in the code explaining what I'm trying to do. I am staring at this code for the 3rd day now non stop - can't see what I'm doing wrong. Please could anyone help me with it? :)

Thanks!

Set up:

$deposits - All the available deposits

$receiptsAsc - All the available receipts sorted by their @ActionDate

Code:

<!-- Accumulate all the deposits with @Status, @DueAmount and @ReceiptDate attributes Provide all deposits, receipts and start with 1st receipt -->
<xsl:variable name="depositsClassified">
    <xsl:call-template name="classifyDeposits">
        <xsl:with-param name="depositsAll" select="$deposits"/>
        <xsl:with-param name="receiptsAll" select="$receiptsAsc"/>
        <xsl:with-param name="receiptCount" select="'1'"/>
    </xsl:call-template>
</xsl:variable>

<!-- Recursive function to associate deposits' total amounts with overall receipts paid
    to determine whether a deposit is due, outstanding or paid. Also determine what's the due amount and latest receipt towards the deposit for each deposit -->
<xsl:template name="classifyDeposits">
    <xsl:param name="depositsAll"/>
    <xsl:param name="receiptsAll"/>
    <xsl:param name="receiptCount"/>

    <!-- If there are deposits to proceed -->
    <xsl:if test="$depositsAll">
        <!-- Get the 1st deposit -->
        <xsl:variable name="deposit" select="$depositsAll[1]"/>
        <!-- Calculate the sum of all receipts up to and including currenly considered -->
        <xsl:variable name="receiptSum">
            <xsl:choose>
                <xsl:when test="$receiptsAll">
                    <xsl:value-of select="sum($receiptsAll[position() &lt;= $receiptCount]/@ReceiptAmount)"/>
                </xsl:when>
                <xsl:otherwise>0</xsl:otherwise>
            </xsl:choose>
        </xsl:variable>
        <!-- Difference between deposit amount and sum of the receipts calculated
        above -->
        <xsl:variable name="diff" select="$deposit/@DepositTotalAmount - $receiptSum"/>

        <xsl:choose>
            <!-- Deposit isn't paid fully and there are more receipts/payments exist.
            So consider the same deposit, but take next receipt into calculation as
            well -->
            <xsl:when test="($diff &gt; 0) and ($receiptCount &lt; count($receiptsAll))">
                <xsl:call-template name="classifyDeposits">
                    <xsl:with-param name="depositsAll" select="$depositsAll"/>
                    <xsl:with-param name="receiptsAll" select="$receiptsAll"/>
                    <xsl:with-param name="receiptCount" select="$receiptCount + 1"/>
                </xsl:call-template>
            </xsl:when>
            <!-- Deposit is paid or we ran out of receipts -->
            <xsl:otherwise>
                <!-- process the deposit. Determine its status and then update
                corresponding 开发者_Go百科attributes -->
                <xsl:apply-templates select="$deposit" mode="defineDeposit">
                    <xsl:with-param name="diff" select="$diff"/>
                    <xsl:with-param name="receiptNum" select="$receiptCount"/>
                </xsl:apply-templates>

                <!-- Recursively call the template with the rest of deposits excluding the first. Before hand update the @ReceiptsAmount. For the receipts before current it is now 0, for the current is what left in the $diff, and simply copy over receipts after current one. -->
                <xsl:variable name="receiptsUpdatedRTF">
                    <xsl:for-each select="$receiptsAll">
                        <xsl:choose>
                            <!-- these receipts was fully accounted for the current deposit. Make them 0 -->
                            <xsl:when test="position() &lt; $receiptCount">
                                <xsl:copy>
                                    <xsl:copy-of select="./@*"/>
                                    <xsl:attribute name="ReceiptAmount">0</xsl:attribute>
                                </xsl:copy>
                            </xsl:when>
                            <!-- this receipt was partly/fully(in case $diff=0) accounted for the current deposit. Make it whatever is in $diff -->
                            <xsl:when test="position() = $receiptCount">
                                <xsl:copy>
                                    <xsl:copy-of select="./@*"/>
                                    <xsl:attribute name="ReceiptAmount">
                                        <xsl:value-of select="format-number($diff, '#.00;#.00')"/>
                                    </xsl:attribute>
                                </xsl:copy>
                            </xsl:when>
                            <!-- these receipts weren't yet considered - copy them over -->
                            <xsl:otherwise>
                                <xsl:copy-of select="."/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each>
                </xsl:variable>
                <xsl:variable name="receiptsUpdated" select="msxsl:node-set($receiptsUpdatedRTF)/Receipts"/>

                <!-- Recursive call for the next deposit. Starting counting receipts from the current one. -->
                <xsl:call-template name="classifyDeposits">
                    <xsl:with-param name="depositsAll" select="$deposits[position() != 1]"/>
                    <xsl:with-param name="receiptsAll" select="$receiptsUpdated"/>
                    <xsl:with-param name="receiptCount" select="$receiptCount"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:if>
</xsl:template>

<!-- Determine deposit's status and due amount -->
<xsl:template match="MultiDeposits" mode="defineDeposit">
    <xsl:param name="diff"/>
    <xsl:param name="receiptNum"/>

    <xsl:choose>
        <xsl:when test="$diff &lt;= 0">
            <xsl:apply-templates select="." mode="addAttrs">
                <xsl:with-param name="status" select="'paid'"/>
                <xsl:with-param name="dueAmount" select="'0'"/>
                <xsl:with-param name="receiptNum" select="$receiptNum"/>
            </xsl:apply-templates>
        </xsl:when>
        <xsl:when test="$diff = ./@DepositTotalAmount">
            <xsl:apply-templates select="." mode="addAttrs">
                <xsl:with-param name="status" select="'due'"/>
                <xsl:with-param name="dueAmount" select="$diff"/>
            </xsl:apply-templates>
        </xsl:when>
        <xsl:when test="$diff &lt; ./@DepositTotalAmount">
            <xsl:apply-templates select="." mode="addAttrs">
                <xsl:with-param name="status" select="'outstanding'"/>
                <xsl:with-param name="dueAmount" select="$diff"/>
                <xsl:with-param name="receiptNum" select="$receiptNum"/>
            </xsl:apply-templates>
        </xsl:when>
        <xsl:otherwise/>
    </xsl:choose>
</xsl:template>

<!-- Add new attributes (@Status, @DueAmount and @ReceiptDate) to the 
    deposit element -->
<xsl:template match="MultiDeposits" mode="addAttrs">
    <xsl:param name="status"/>
    <xsl:param name="dueAmount"/>
    <xsl:param name="receiptNum" select="''"/>

    <xsl:copy>
        <xsl:copy-of select="./@*"/>
        <xsl:attribute name="Status"><xsl:value-of select="$status"/></xsl:attribute>
        <xsl:attribute name="DueAmount"><xsl:value-of select="$dueAmount"/></xsl:attribute>
        <xsl:if test="$receiptNum != ''">
            <xsl:attribute name="ReceiptDate">
                <xsl:value-of select="$receiptsAsc[position() = $receiptNum]/@ActionDate"/>
            </xsl:attribute>
        </xsl:if>
        <xsl:copy-of select="./*"/>
    </xsl:copy>
</xsl:template>


Interesting question. I believe a better approach is to add a parameter to accumulate the balance, so that you will not need to update the receipts structure. My version follows. I have tested it on the sample input you provided, and received the expected result.

Please note that I changed the template pattern for deposits from MultiDeposits to Deposits, since you used the latter element name in your sample input.

<!-- Accumulate all the deposits with @Status, @DueAmount and @ReceiptDate 
     attributes. Provide all deposits and receipts. --> 
<xsl:variable name="depositsClassified"> 
    <xsl:call-template name="classifyDeposits"> 
        <xsl:with-param name="depositsAll" select="$deposits"/> 
        <xsl:with-param name="receiptsAll" select="$receiptsAsc"/> 
    </xsl:call-template> 
</xsl:variable> 

<!-- Recursive function to associate deposits' total amounts with overall 
     receipts paid to determine whether a deposit is due, outstanding or paid. 
     Also determine what's the due amount and latest receipt towards the 
     deposit for each deposit --> 
<xsl:template name="classifyDeposits"> 
    <xsl:param name="depositsAll"/> 
    <xsl:param name="receiptsAll"/> 
    <xsl:param name="balance" select="0"/>

    <!-- If there are deposits to proceed --> 
    <xsl:if test="$depositsAll"> 
        <!-- Get the 1st deposit --> 
        <xsl:variable name="deposit" select="$depositsAll[1]"/> 
        <!-- Get the 1st receipt. -->
        <xsl:variable name="receipt" select="$receiptsAll[1]"/> 
        <!-- Calculate difference. --> 
        <xsl:variable 
            name="diff" 
            select="$balance + $deposit/@DepositTotalAmount
                             - $receipt/@ReceiptAmount"/> 

        <xsl:choose> 
            <!-- Deposit isn't paid fully and there are more receipts. 
                 Move on to the next receipt, with updated balance. --> 
            <xsl:when test="($diff &gt; 0) and $receiptsAll[2]"> 
                <xsl:call-template name="classifyDeposits"> 
                    <xsl:with-param name="depositsAll" select="$depositsAll"/> 
                    <xsl:with-param 
                        name="receiptsAll" 
                        select="$receiptsAll[position() != 1]"/> 
                    <xsl:with-param 
                        name="balance" 
                        select="$balance - $receipt/@ReceiptAmount"/>
                </xsl:call-template> 
            </xsl:when> 
            <!-- Deposit is paid or we ran out of receipts --> 
            <xsl:otherwise> 
                <!-- Process the deposit. Determine its status and then update 
                corresponding attributes --> 
                <xsl:apply-templates select="$deposit" mode="defineDeposit"> 
                    <xsl:with-param name="diff" select="$diff"/> 
                    <xsl:with-param name="receipt" select="$receipt"/> 
                </xsl:apply-templates> 

                <!-- Recursive call for the next deposit. --> 
                <xsl:call-template name="classifyDeposits"> 
                    <xsl:with-param
                        name="depositsAll" 
                        select="$depositsAll[position() != 1]"/> 
                    <xsl:with-param name="receiptsAll" select="$receiptsAll"/> 
                    <xsl:with-param 
                        name="balance" 
                        select="$balance + $deposit/@DepositTotalAmount"/>
                </xsl:call-template> 
            </xsl:otherwise> 
        </xsl:choose> 
    </xsl:if> 
</xsl:template> 

<!-- Output deposit's status and due amount --> 
<xsl:template match="Deposits" mode="defineDeposit"> 
    <xsl:param name="diff"/> 
    <xsl:param name="receipt"/> 
    <xsl:copy>
        <xsl:copy-of select="@*"/> 
        <xsl:choose>
            <xsl:when test="$diff &gt;= @DepositTotalAmount">
                <xsl:attribute name="Status">due</xsl:attribute>
                <xsl:attribute name="DueAmount">
                    <xsl:value-of select="@DepositTotalAmount"/>
                </xsl:attribute>
            </xsl:when>
            <xsl:when test="$diff &gt; 0">
                <xsl:attribute name="Status">outstanding</xsl:attribute>
                <xsl:attribute name="DueAmount">
                    <xsl:value-of select="$diff"/>
                </xsl:attribute>
            </xsl:when>
            <xsl:otherwise>
                <xsl:attribute name="Status">paid</xsl:attribute>
                <xsl:attribute name="DueAmount">0</xsl:attribute>
                <xsl:attribute name="ReceiptDate">
                    <xsl:value-of select="$receipt/@ActionDate"/>
                </xsl:attribute>
            </xsl:otherwise>
        </xsl:choose>
        <xsl:copy-of select="node()"/> 
    </xsl:copy>
</xsl:template> 

As an example, here's how a recursion develops for the input:

Deposit 1 = 2200, Deposit 2 = 1100
Receipt 1 = 200, Receipt 2 = 2000, Receipt 3 = 800

    1Run) bal = 0; 
          diff = 0(bal) + 2200(dep1) - 200(recp1) = 2000
          (diff > 0 & more receipts)

    2Run) bal = 0-200(recp1) = -200; 
          diff = -200(bal) + 2200(dep1) - 2000(recp2) = 0
          (output first deposit: status = "paid", proceed to next deposit)

    3Run) bal= -200 + 2200(dep1) = 2000;
          diff = 2000(bal) + 1100(dep2) -2000(recp2) = 1100
          (diff > 0 & more receipts) 

    4Run) bal= 2000 - 2000(recp2) = 0;
          diff = 0(bal) + 1100(dep2) - 800(recp3) = 300
          (no more receipts, output second deposit: status = "outstanding")


However there isn't any identification what receipt was paid toward what deposit.

This is the key to your problem. You have to have a way to associate the receipts with the deposits UP FRONT. Imagine if you were working with paper receipts, how would you handle this? You'd have to ask the person who gave you the receipt how much was intended for which deposit, and then once you found that out you record it on the receipt. Once you know this and reflect it in the way you represent receipts, you can build the xslt to grab those bits out. Unfortunately I can't help you with the xslt for that, but imagine that each receipt has child element for each partition. like:

<RECEIPTS total=500 blah blah blah>
      <subtotal deposit=1 amount=100>
      <subtotal deposit=2 amount=300>
</RECEIPTS>

then as you loop through, grab the children of the receipt, loop through each subtotal and add it to the appropriate counter for the deposit sum.

Also, I noticed, from your desired output, what happens if more than one receipt is applied towards a deposit? how do you represent that? currently you have

Deposit 2 is partly paid (status=outstanding, dueAmount=50, receiptNum=3

what if deposit 2 was partially paid with 2 receipts, is the attribute receiptNum still going to have meaning for you? you might have to extend this, maybe by adding subtotal children elements in the same manner as the receipts model I offered earlier.

Id say if you want to get a handle on this, pretend you were doing this all with/on paper. That would shed light on how you need to do it in code.

After looking at some of your other posts, I realize that you may not be in control of the dataset you get. At some point however, you need to be able to answer the question, "Which amounts of these receipts go to which deposits?" After that, I have to say, your attempts at using recursion to solve this problem might be serving only to confuse you. Any recursion method can be replaced with a loop instead. I look forward to seeing what your final solution looks like.

0

精彩评论

暂无评论...
验证码 换一张
取 消