QR-Code Invoice

QR-Code Invoice

QR-Code Invoice Implementation Guide

Overview

This document outlines the functional and technical flow for implementing QR-based import in the Invoice module. The feature is controlled by the system parameter EnableQR. When this parameter is ON, additional QR-specific logic will be executed during Invoice line import operations.

Changes in Invoice1Index

  • Need to Hide Create Line and Create From Order button if Enable QR is Y
  • Need to add Import Button for Importing Order

Invoice1Import New Page

  • It will have Invoice No, Invoice Date , Party Name as readonly
  • It will have Import, Download Model and Pending Order Report Button
  • Use of every feature is as follow
    Import - For Importing the Model
    Download Model - It will download the model with 3 columns IssueQty, Orders1id, QrDescription
    Pending Order Report - It will ask user for Order No, Po No, YearLabel and then download a Pending Order report with Po No and Order No at top and ItemId, Orders1Id, Qty columns in report (column name need to be confirmed)

Pending Order Report

Functionality:

  • User fills Order No / PO No in the input field
  • Upon clicking “Pending Order Report” button, generates Excel report with:
    • PO No and/or Order No displayed at the top of the report
    • Report body with columns: Orders1Id,OrderNo,OrderDate, Qty
    • Data filtered based on provided Order No / PO No parameter
      • We will use Invoice1CreateIndex

Flow of Import

  1. User Creates main Level for Invoice
  2. Data Preparation: User fills Order1Id From the pending order report and then they will scan the QR and add in QR descriptions and if they want to issue fullqty then they will keep the issue qty as blanck if partial qty needs to be used then tey will fill IssueQty cant be 0 and there is no provision of Deletions marked with ‘D’ in IssueQty column
  3. They have to upload the scanned Qr Description and if they want partial they can enter in Issue Qty, if they want full they can leave it blank
  4. They can upload J-Trnid, G-Trnid, S-Trnid, M-Trnid
  5. They can Upload for multiple Orders also and single orders also
  6. Grouping Logic: Records inserted based on ItemId and Trnid grouping (GROUP BY ItemId). If there is 2 trnid like M-000001 and S-000001 but both is of ItemId 000001 then we will have 1 row only by groupping by itemid and sumofqty will be qty
  7. ItemId and Order No combination must exist in System
  8. Print Label feature will not be there in Invoice
  9. Other columns of Invoice we can take from the Orders1Id
  10. Need to handle all the table changes we have done in Invoice1Create
  11. Sub-Transaction Logic: Scanned sub-trn logic based on doctype and Doc Id. IN ScannedSubTrn we will add 2 new column doctype and DocId and no chnages is needed in the Invocie2table and we will insert the created InvoiceId and doctype as I in ScannedSubTrn like M-000001-001 and S-000001-001 has same ItemId 000001 so one line in invoice2 will be created so we will take that id and we will insert 2 line in ScannedSubTrn and for both docid will be invoice2table id and if J-000001 is one more line in that case same id of the 2nd lineof invoice2 will be inserted in with doctype as I
  12. Rollback Handling : for eg the the id 1034 of invoice2 table which is invoiceline is delted then we will update the both subtrn balace M-000001-001 and S-000001-001 with whatever ussed in that invocie
  13. Button condition : Invoice is not cancelled, Invoice is not E-Invoice, QR is Enable

Validations

Z-Series Validations (Controller/Excel Validations)

Error CodeDescription
Z1Issue Qty must be a numeric value. If you want to issue the full quantity, leave it blank. Enter D if you do not want to issue that Trn Id.
Z2Orders1id must be Number
Z3QR Description can be upto 500 characters only

Note: File format validation (column count != 4 → “Please upload correct model”) and empty file validation (rowcount == 1 → “Uploaded XL File does not contain any data”) are handled in the UI like JobWorkController.cs, not as error codes.

A-Series Validations (Repository Validations)

Error CodeDescription
AOnly GRN, JobWork, Material, and Stock transactions can be imported
BInvalid Trn Id or Sub Trn Id
CEntered Qty exceeds available balance
DIssue Qty must be between 0.001 and 999999.999
EMultiple Sales Orders in a Single Invoice is disabled for this Party
FGST tax code for all lines must be same
GInvoiced Qty can not exceed pending Order qty
HCurrency as per Sales order and currency as per Invoice is mismatched.
IInvalid Order No or does not belong to this party/branch/unit
JInvalid Orders Id
KOrder date should be less than or equal to Invoice date.
LOrder type must be Direct / Confirmed Order.
MStatus of all lines in this Order must be OK.
NOne or more items in this order are not yet released for sale.
OPayment Terms must be same for all the lines
PQty can not be more than {excessOrderQty} i.e. Pending Order Qty plus 10%
QQR Item and Order Item are mismatching
RItem belongs to Freight Item
STrn Id belongs to rejected item

Invoice1Import Method Implementation

public async Task<Tuple<List<InvoiceLine>, string>> Invoice1Import(List<InvoiceLine> listdata, string dbname, string userName, string yearLabel, int mainId)
{
    var tempcreation = @"CREATE TABLE #invoice
    (
        id INT PRIMARY KEY IDENTITY(1,1),
        QrDescription NVARCHAR(500) COLLATE DATABASE_DEFAULT,
        qty NUMERIC(10,3),
        orders1id INT,
        trnid NVARCHAR(7) COLLATE DATABASE_DEFAULT,
        subtrnid NVARCHAR(3) COLLATE DATABASE_DEFAULT,
        itemid NVARCHAR(6) COLLATE DATABASE_DEFAULT,
        errorcode NVARCHAR(20) COLLATE DATABASE_DEFAULT
    )";

    var inserttemp = @"INSERT INTO #invoice(QrDescription, qty, orders1id, trnid, subtrnid)
    VALUES(@QrDescription, @qty, @orders1id, @trnid, @subtrnid)";

    var updateQty = @"UPDATE #invoice SET qty = Q.balanceqty
    FROM QrSubTrn Q
    WHERE #invoice.trnid = Q.trnid AND #invoice.subtrnid = Q.subtrnid AND COALESCE(#invoice.qty, 0) = 0";

    var updateItemId = @"WITH A(trnid, itemid) AS
    (
        SELECT I2.TrnId, I2.ItemId
        FROM Invoice2 I2
        LEFT JOIN #invoice ON I2.TrnId = #invoice.TrnId

        UNION ALL

        SELECT G1.TrnId, G1.ItemId
        FROM Grn1 G1
        LEFT JOIN #invoice ON G1.TrnId = #invoice.TrnId

        UNION ALL

        SELECT Q.TrnId, Q.ItemId
        FROM QRStock Q
        LEFT JOIN #invoice ON Q.TrnId = #invoice.TrnId

        UNION ALL

        SELECT J1.TrnId, J1.ItemId
        FROM JobWork1 J1
        LEFT JOIN #invoice ON J1.TrnId = #invoice.TrnId

        UNION ALL

        SELECT M1.TrnId, M1.ItemId
        FROM Mirs1 M1
        LEFT JOIN #invoice ON M1.TrnId = #invoice.TrnId
    )
    UPDATE #invoice SET itemid = A.itemid
    FROM A
    WHERE #invoice.trnid = A.trnid";

    // Error Code A: Only GRN, JobWork, Material, and Stock transactions
    var errorcodeA = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode),'')+ ' A'
    WHERE LEFT(trnid, 1) NOT IN ('G','J','M','S')";

    // Error Code B: Invalid Trn Id or Sub Trn Id
    var errorcodeB = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' B'
    WHERE NOT EXISTS (SELECT trnid + subtrnid FROM QrSubTrn WHERE #invoice.trnid + #invoice.subtrnid = QrSubTrn.trnid + QrSubTrn.subtrnid)";

    // Error Code C: Entered Qty exceeds available balance
    var errorcodeC = @"WITH A(trnid, subtrnid, balanceqty) AS
    (
        SELECT Q.trnid, Q.subtrnid, Q.balanceqty FROM #invoice I
        LEFT JOIN QrSubTrn Q ON I.trnid = Q.trnid AND I.subtrnid = Q.subtrnid
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode), '') + ' C'
    FROM A
    WHERE #invoice.trnid = A.trnid AND #invoice.subtrnid = A.subtrnid AND #invoice.qty > A.balanceqty";

    // Error Code D: Issue Qty must be between 0.001 and 999999.999
    var errorcodeD = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' D'
    FROM #invoice
    WHERE (qty NOT BETWEEN 0.001 AND 999999.999)";

    // Error Code E: Multiple Sales Orders in a Single Invoice is disabled for this Party
    var errorcodeE = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode), '') + ' E'
    WHERE COALESCE(multipleorders, 'N') = 'Y' AND partyid = @partyid ";

    // Error Code F: GST tax code for all lines must be same
    var errorcodeF = @"WITH A(gstcode) AS
    (
        SELECT O1.gstcode
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
    ),
    B(gstcode, gstcount) AS
    (
        SELECT gstcode, COUNT(*) FROM A GROUP BY gstcode
    ),
    C(existinggstcode) AS
    (
        SELECT TOP 1 gstcode FROM Invoice2 WHERE yearinvoiceno = @yearinvoiceno
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode), '') + ' F'
    WHERE (SELECT COUNT(*) FROM B) > 1 OR EXISTS
    (SELECT gstcode FROM B WHERE gstcode != (SELECT existinggstcode FROM C))";

    // Error Code G: Invoiced Qty can not exceed pending Order qty
    var errorcodeG = @"WITH A(orders1id, pendingqty) AS
    (
        SELECT O1.id, O1.qty
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' G'
    FROM A
    WHERE #invoice.orders1id = A.orders1id AND #invoice.qty > A.pendingqty";

    // Error Code H: Currency as per Sales order and currency as per Invoice is mismatched
    var errorcodeH = @"WITH A(orders1id, ordercurrency) AS
    (
        SELECT orders1id, O.currency
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
        LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' H'
    FROM A
    WHERE #invoice.orders1id = A.orders1id AND A.ordercurrency != @currency";

    // Error Code I: Invalid Order No or does not belong to this party/branch/unit
    // We don't need to cheeck the party/branch/unit mismatch from trnid's as invoice and order should match ,doesnt matter the trnid belogs to which party/branch/unit as we can procure the items as GRN/Jobwork/Material/Stock from any party. So only checking the orders1id's party/branch/unit with invoice party/branch/unit
    var errorcodeI = @"WITH A(orders1id, partybranchid, unitcode) AS
    (
        SELECT I.orders1id, O.partyid + O.branchid, O.unitcode
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
        LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
    ),
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' I'
    FROM A
    WHERE #invoice.orders1id = A.orders1id AND (A.partybranchid != @partybranchid OR A.unitcode != @unitcode)";

    // Error Code J: Invalid Orders Id
    var errorcodeJ = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' J'
    WHERE NOT EXISTS (SELECT id FROM Orders1 WHERE #invoice.orders1id = Orders1.id)";

    // Error Code K: Order date should be less than or equal to Invoice date
    var errorcodeK = @"WITH A(orders1id, orderdate) AS
    (
        SELECT Orders1.id, O.dated
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
        LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' K'
    FROM A
    WHERE #invoice.orders1id = A.orders1id AND A.orderdate > @invoicedate";

    // Error Code L: Order type must be Direct / Confirmed Order
    var errorcodeL = @"WITH A(orders1id, isconfirmed) AS
    (
        SELECT O1.id, O.IsConfirmedOrder
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
        LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
        WHERE isconfirmed = 'N'
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' L'
    FROM A
    WHERE #invoice.orders1id = A.orders1id";

    // Error Code M: Status of all lines in this Order must be OK
    var errorcodeM = @"WITH A(orders1id, orderstatus) AS
    (
        SELECT O1.id, O1.orderstatus
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
        LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
        WHERE orderstatus != '00'
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' M'
    FROM A
    WHERE #invoice.orders1id = A.orders1id";

    // Error Code N: One or more items in this order are not yet released for sale
    var errorcodeN = @"WITH A(orders1id, releasestatus) AS
    (
        SELECT Orders1id, IM.ReleaseStatus
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
        LEFT JOIN InventoryMst IM ON O1.itemid = IM.itemid
        WHERE releasestatus = 'N'
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' N'
    FROM A
    WHERE #invoice.orders1id = A.orders1id ";

    // Error Code O: Payment Terms must be same for all the lines
    var errorcodeO = @"WITH A(orders1id, paymentterm) AS
    (
        SELECT Orders1id, O.otermcode
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
        LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
    ),
    B(paymentterm, termcount) AS
    (
        SELECT paymentterm, COUNT(*) FROM A GROUP BY paymentterm
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode), '') + ' O'
    WHERE (SELECT COUNT(*) FROM B) > 1";

    // Error Code P: Qty can not be more than {excessOrderQty} i.e. Pending Order Qty plus 10%
    var errorcodeP = @"WITH A(orders1id, pendingqty) AS
    (
        SELECT Orders1id, O1.qty
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' P'
    FROM A
    WHERE #invoice.orders1id = A.orders1id AND #invoice.qty > (A.pendingqty * 1.1)";

    // Error Code Q: QR Item and Order Item are mismatching
    var errorcodeQ = @"WITH A(orders1id, orderitemid) AS
    (
        SELECT Orders1id, O1.itemid
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
    )
    UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' Q'
    FROM A
    WHERE #invoice.orders1id = A.orders1id AND #invoice.itemid != A.orderitemid";


    // Error Code R: Item belongs to Freight Item
    var errorcodeR = @"
    WITH A (orders1id) AS
    (
        SELECT DISTINCT O1.id
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
        LEFT JOIN InventoryMst IM ON O1.itemid = IM.itemid
        WHERE IM.classid = 'FP'
    )
    UPDATE #invoice
    SET errorcode = COALESCE(RTRIM(errorcode), '') + ' R'
    FROM A
    WHERE #invoice.orders1id = A.orders1id;";

    // Error Code S: Trn Id belongs to rejected item
    var errorcodeS = @"
    WITH A (trnid) AS
    (
        SELECT DISTINCT I.trnid
        FROM #invoice I
        INNER JOIN QRStock Q ON Q.trnid = I.trnid
        WHERE LEFT(I.trnid, 1) = 'S'
        AND Q.itemstate = 'R'
    )
    UPDATE #invoice
    SET errorcode = COALESCE(RTRIM(errorcode), '') + ' S'
    FROM A
    WHERE #invoice.trnid = A.trnid;";


    var cnterror = @"SELECT COUNT(Errorcode) AS CNT FROM #invoice
    WHERE COALESCE(ERRORCODE,'') != ''";

    var data = @"SELECT I.QrDescription, I.qty, I.orders1id,
    COALESCE(I.errorcode,'') AS errorcode
    FROM #invoice I
    ORDER BY I.id ASC";

    var invoiceinsert = @"WITH A (itemid, totalqty, orders1id, rate, gstcode, stage, storecode, brandid) AS
    (
        SELECT O1.itemid, SUM(I.qty) AS totalqty, I.orders1id,
        CAST(ROUND(O1.rate * @exchangeRate,3) AS NUMERIC (12, 4)) AS Rate, O1.gstcode, O1.stage, O1.storecode, O1.brandid
        FROM #invoice I
        LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
        GROUP BY O1.itemid, I.orders1id, O1.rate, O1.gstcode, O1.stage, O1.storecode, O1.brandid
        ORDER BY O1.itemid, O1.storecode, O1.brandid
    ),
    B(itemid, totalqty, orders1id, rate, gstcode, stage, storecode, brandid, value) AS
    (
        SELECT itemid, totalqty, orders1id, rate, gstcode, stage, storecode, brandid, CAST(ROUND(rate * totalqty,2) AS NUMERIC (11,2)) AS value
        FROM A
    )
    INSERT INTO Invoice2
    (
        invoiceno, itemid, qty, orders1id, rate,
        value, yearinvoiceno, Itemstate, stage, storecode, gstcode, packingslip,noofpacket
    )
    SELECT @invoiceno, B.itemid, B.totalqty, B.orders1id, B.rate, B.value, @yearinvoiceno, 'G' AS Itemstate,
    B.stage, B.storecode, B.gstcode, '', 0
    FROM B;";

    // Order quantity management after invoice insertion
    var updateOrderQtyValue = @"WITH A(orders1id, totalinvoicedqty) AS
    (
        SELECT orders1id, SUM(qty) AS totalinvoicedqty
        FROM #invoice
        GROUP BY orders1id
    ),
    B (orders1id,newqty,newvalue) AS
    (
        SELECT
        O1.id AS orders1id,
        IIF(O1.qty > A.totalinvoicedqty, O1.qty - A.totalinvoicedqty, 0) AS newqty,
        IIF(O1.qty > A.totalinvoicedqty, O1.value - (A.totalinvoicedqty * O1.rate), 0) AS newvalue
        FROM A
        INNER JOIN orders1 O1 ON A.orders1id = O1.id
    )
    UPDATE O1
    SET qty = B.newqty,
    value = B.newvalue
    FROM orders1 O1
    LEFT JOIN B ON O1.id = B.orders1id";

    var createNewOrders1 = @"WITH A(orders1id, totalinvoicedqty) AS
    (
        SELECT orders1id, SUM(qty) as totalinvoicedqty
        FROM #invoice
        GROUP BY orders1id
    ),
    B(orders1id, remainingqty, remainingvalue) AS
    (
        SELECT O1.id, O1.qty - A.totalinvoicedqty AS remainingqty,
        CAST(O1.value - ROUND((A.totalinvoicedqty * O1.rate),2) AS NUMERIC(11,2)) AS remainingvalue
        FROM Orders1 O1
        LEFT JOIN A ON O1.id = A.orders1id
        WHERE O1.qty > A.totalinvoicedqty
    ),
    C(orderno, itemid, qty,
    rate, value, reqdby, planneddate, isnonstd, nonstdnote, orderstatus, effectivedate, notes,
    yearorderno, brandid, MRP, leadtime, gstcode, UserQty, bookingno) AS
    (
        SELECT O1.orderno, O1.itemid, B.remainingqty, O1.rate, B.remainingvalue, O1.reqdby,
        O1.planneddate, O1.isnonstd, O1.nonstdnote, O1.orderstatus, O1.effectivedate, O1.notes,
        O1.yearorderno, O1.brandid, O1.MRP, O1.leadtime, O1.gstcode, O1.UserQty, O1.bookingno
        FROM Orders1 O1
        LEFT JOIN B ON O1.id = B.orders1id
    )
    INSERT INTO Orders1 (orderno, itemid, qty,
    rate, value, reqdby, planneddate, isnonstd, nonstdnote, orderstatus, effectivedate, notes,
    yearorderno, brandid, MRP, leadtime, gstcode, UserQty, bookingno)
    SELECT orderno, itemid, qty,
    rate, value, reqdby, planneddate, isnonstd, nonstdnote, orderstatus, effectivedate, notes,
    yearorderno, brandid, MRP, leadtime, gstcode, UserQty, bookingno
    FROM C";
    
    // Needs to be discussed UD Point
    var updateAlOrdersLink = @"WITH A(orders1id, totalinvoicedqty) AS
    (
        SELECT orders1id, SUM(qty) as totalinvoicedqty
        FROM #invoice
        GROUP BY orders1id
    )
    UPDATE ALOrdersLink
    SET qty = A.totalinvoicedqty
    FROM ALOrdersLink AL
    INNER JOIN A ON AL.orders1id = A.orders1id";

    // Needs to be discussed UD Point
    var insertNewAlOrdersLink = @"WITH A(orders1id, totalinvoicedqty) AS
    (
        SELECT orders1id, SUM(qty) as totalinvoicedqty
        FROM #invoice
        GROUP BY orders1id
    ),
    B(oldorders1id, new_orders1id) AS
    (
        SELECT O1.id as oldorders1id, NO1.id as new_orders1id
        FROM Orders1 O1
        INNER JOIN Orders1 NO1 ON O1.orderno = NO1.orderno AND O1.itemid = NO1.itemid
        AND O1.yearorderno = NO1.yearorderno
        INNER JOIN A ON O1.id = A.orders1id
        WHERE O1.qty = 0 AND NO1.qty > 0
    )
    INSERT INTO ALOrdersLink (al1id, orders1id, qty)
    SELECT AL.al1id, B.new_orders1id, AL.qty - A.totalinvoicedqty
    FROM ALOrdersLink AL
    INNER JOIN A ON AL.orders1id = A.orders1id
    INNER JOIN B ON AL.orders1id = B.oldorders1id";

    var scannedsubtrninsert = @"WITH A(trnid, subtrnid, qty, yeardocno, isrolledback, docid, doctype) AS
    (
        SELECT I.trnid, I.subtrnid, I.qty, @yearinvoiceno AS yeardocno, 'N' AS isrolledback,
        I2.id AS docid, 'I' AS doctype
        FROM #invoice I
        LEFT JOIN Invoice2 I2 ON I.orders1id = I2.orders1id
    )
    INSERT INTO ScannedSubTrn (trnid, subtrnid, qty, yeardocno, isrolledback, docid, doctype)
    SELECT trnid, subtrnid, qty, yeardocno, isrolledback, docid, doctype FROM A";

    var updateQrSubTrnQty = @"WITH A(TrnId,SubTrnId,qty) AS
    (SELECT TrnId,SubTrnId,qty FROM #invoice)
    UPDATE QrSubTrn SET BalanceQty = BalanceQty - A.qty FROM A
    WHERE QrSubTrn.TrnId = A.TrnId AND QrSubTrn.SubTrnId = A.SubTrnId";

    var expInvoiceCntQuery = @"SELECT COUNT(*) AS Cnt1 FROM InvoiceE1 WHERE Yearinvoiceno=@yearinvoiceno";

    // need to discuss
    var insertInvoiceE2 = @"INSERT INTO InvoiceE2 (yearinvoiceno,invoice2id,Palletno,Series,Qtyperbox,Boxes,Netweight,Grossweight)
        VALUES(@yearinvoiceno, @invoice2id, @Palletno, @Palletno, @Series, @Series, @Series, @Series)";

    // need to discuss
    var updateDueDate = @"UPDATE Invoice SET Duedate=@duedate
        WHERE Yearinvoiceno=@yearinvoiceno";

    var updateEInSupType = @"UPDATE Invoice1 SET EIn_SupType=@eInSupType
        WHERE Yearinvoiceno=@yearinvoiceno";

    var lognotes = @"WITH A (trnid) AS
    (
        SELECT DISTINCT trnid FROM #invoice
    )
    SELECT STRING_AGG(trnid,' ') AS notes FROM A";

    using var connection = _DapperContext.SetClientConnection(dbname);
    connection.Open();
    var invoiceDetails = GetInvoiceInfo(mainId, dbname);
    connection.Execute(tempcreation);

    connection.Execute(inserttemp,
        listdata.Select(dataLine => new
        {
            dataLine.QrDescription,
            SubTrnId = dataLine.QrDescription.Trim().Substring(8, 3),
            TrnId = dataLine.QrDescription.Trim()[..7],
            qty = dataLine.Qty,
            orders1id = dataLine.Orders1Id
        })
    );

    connection.Execute(updateQty);
    connection.Execute(updateItemId);

    connection.Execute(errorcodeA);
    connection.Execute(errorcodeB);
    connection.Execute(errorcodeC);
    connection.Execute(errorcodeD);
    connection.Execute(errorcodeE, new { partyid = invoiceDetails.PartyId });
    connection.Execute(errorcodeF, new{yearinvoiceno = invoiceDetails.YearInvoiceNo});
    connection.Execute(errorcodeG);
    connection.Execute(errorcodeH, new { currency = invoiceDetails.Currency });
    connection.Execute(errorcodeI, new { partybranchid = invoiceDetails.PartyId + invoiceDetails.BranchId, unitcode = invoiceDetails.UnitCode });
    connection.Execute(errorcodeJ);
    connection.Execute(errorcodeK, new { invoicedate = invoiceDetails.Dated });
    connection.Execute(errorcodeL);
    connection.Execute(errorcodeM);
    connection.Execute(errorcodeN);
    connection.Execute(errorcodeO);
    connection.Execute(errorcodeP);
    connection.Execute(errorcodeQ);

    var errorcount = connection.QuerySingleOrDefault<int>(cnterror);
    string? msg;
    msg = "Import could not succeed because of errors in input";
    if (errorcount == 0)
    {
        connection.Execute(invoiceinsert,
        new
        {
            yearinvoiceno = invoiceDetails.YearInvoiceNo,
            invoiceno = invoiceDetails.InvoiceNo
        });

        connection.Execute(updateOrderQtyValue);
        connection.Execute(createNewOrders1);
        connection.Execute(updateAlOrdersLink);
        connection.Execute(insertNewAlOrdersLink);
        connection.Execute(scannedsubtrninsert, new { yearinvoiceno =  invoiceDetails.YearInvoiceNo });
        connection.Execute(updateQrSubTrnQty);

        connection.Execute(updateDueDate,new{duedate = DateTime.Now.Date, yearinvoiceno = invoiceDetails.YearInvoiceNo});

        var expcnt = connection.QuerySingleOrDefault<int>(expInvoiceCntQuery, new { yearinvoiceno = invoiceDetails.YearInvoiceNo });
        if (expcnt == 0 )
        {
            connection.Execute(insertInvoiceE2, new { yearinvoiceno = invoiceDetails.YearInvoiceNo, Palletno = "0", Series = 0, Qtyperbox = 0, Boxes = 0, Netweight = 0, Grossweight = 0 });
        }

        var firstLineInfo = Get1stLineInvoiceInfo(invoiceDetails.YearInvoiceNo, dbname);
        var gstRate = GetGstRate(firstLineInfo.GstRateCode, dbname);
        var eInSupType = gstRate > 0 ? "WP" : "WOP";
        connection.Execute(updateEInSupType, new { eInSupType, yearinvoiceno = invoiceDetails.YearInvoiceNo });

        ComputeInvoiceValues(mainId, dbname);

        var notes = connection.QuerySingleOrDefault<string>(lognotes);
        _IUtilityMethodsRepository.InsertMFGLog(dbname, userName, "000000", "Invoice", "Import", yearLabel, notes);
        msg = "Invoice import succeeded.";
    }
    var finaldata = connection.Query<InvoiceLine>(data);
    var data1 = finaldata.ToList();

    return new Tuple<List<InvoiceLine>, string>(data1, msg);
}

Database Schema Considerations

New Tables Required

  • ScannedSubTrn: For rollback logic tracking scanned sub-transactions (add docid INT and doctype NVARCHAR(2))

API Endpoints to Add

Invoice Controller Extensions

  • POST /api/Invoice/Invoice1Import: Process uploaded QR import file
  • GET /api/Invoice/DownloadModel: Generate and return Excel template
  • POST /api/Invoice/PendingOrderReport: Create and return pending order Excel report
  • GET /api/Invoice/Invoice1ValidateQrImport: Pre-validate import data before processing

Repository Methods Required

  • Invoice1Import(): Main import processing method with validation logic
  • PendingOrderReport(): Build pending order report
  • Invoice1QrRollback(): Rollback method for delete operations

Processing Logic

Import Execution Steps

  1. File Validation: Check format, columns, and data types (handled like JobWorkController.cs - columncount != 4 → “Please upload correct model”, rowcount == 1 → “Uploaded XL File does not contain any data”)
  2. Data Parsing: Extract TrnId, SubTrnId from QR descriptions, Orders1Id comes as separate column data from the import
  3. Order Validation: Verify Orders1Id exists and belongs to correct order
  4. Quantity Resolution: Use full quantity if IssueQty blank, validate partial quantities, handle ‘D’ for skipping transactions
  5. Grouping: Group by ItemId, sum quantities for same ItemId across different TrnIds
  6. Business Rules: Apply all A-series validations
  7. Data Insertion: Create Invoice2 records with grouped data
  8. Sub-Transaction Tracking: Insert ScannedSubTrn records with docid (invoice2 id) and doctype (‘I’)
  9. Balance Updates: Update QR sub-transaction balances
  10. Audit Logging: Log import operation with transaction details

Rollback Handling

  • Track imported transactions in ScannedSubTrn table with docid and doctype
  • When invoice2 line is deleted, restore QR balances for all related sub-transactions
  • Mark rollback status to prevent duplicate processing

UI/UX Considerations

User Workflow

  • Download template then Download pending order report
  • Fill Orders1Id From pending order report to Download template
  • Leave IssueQty blank for full quantity or enter partial quantity or enter D if

Error Handling

  • Clear error codes and descriptions
  • Allow re-upload after fixing issues
  • Provide detailed validation feedback
  • Support partial success scenarios

This documentation prioritizes the Invoice QR approach as specified in InvoiceQr.md, highlighting the key differences from Jobwork QR implementation.

Methods / File Name

  • Invoice1Import
  • RollBack
  • PendingOrderReport
  • Invoice1ImportDownloadModel
// New property in InvoiceLine
public string QrDescription { get; set; } = string.Empty;
public string XlFileName { get; set; } = string.Empty;
public string ErrorCode { get; set; } = string.Empty;
public int Orders1Id { get; set; }