Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/extraScripts/migration/holoo2hesabix/Services/HolooDocumentReader.vb
Babak Alizadeh a5875e7a3a Release Holoo2Hesabix 1.1.0 with production-ready accounting migration.
Harden Holoo-to-Hesabix document partition/mapping, sample E2E tooling, remove hardcoded SQL credentials from scripts, fix income AR account 10401, and publish Setup MSI 1.1.0.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 15:56:40 +03:30

856 lines
41 KiB
VB.net

Imports System.Data.SqlClient
Friend Class HolooInvoiceHeader
Public Property FacCode As String
Public Property FacType As String
Public Property CustomerCode As String
Public Property FacDate As Date
Public Property SumPrice As Double
Public Property Takhfif As Double
Public Property SumLevy As Double
Public Property SumScot As Double
Public Property FNaghd As Double
Public Property FCheck As Double
Public Property FNesieh As Double
Public Property Card As Double
Public Property FHaval As Double
Public Property Comment As String
Public Property SanadCode As Integer
Public Property Lines As New List(Of HolooInvoiceLine)
Public ReadOnly Property Key As String
Get
Return FacType & ":" & FacCode
End Get
End Property
End Class
Friend Class HolooInvoiceLine
Public Property ArticleCode As String
Public Property Quantity As Double
Public Property UnitPrice As Double
Public Property ForeignUnitPrice As Double
Public Property LineDiscount As Double
Public Property Levy As Double
Public Property Scot As Double
Public Property MoneyCode As Integer
Public Property Comment As String
End Class
Friend Class HolooInvoiceSettlementHint
Public Property SanadCode As Integer
Public Property CashCol As String
Public Property CashMoien As String
Public Property CashTafzili As String
Public Property CashName As String
Public Property BankCol As String
Public Property BankMoien As String
Public Property BankTafzili As String
Public Property BankName As String
End Class
Friend Class HolooOpeningLine
Public Property ColCode As String
Public Property MoienCode As String
Public Property TafziliCode As String
Public Property SarfaslName As String
Public Property Debit As Double
Public Property Credit As Double
Public Property Comment As String
End Class
Friend Class HolooCheckRow
Public Property CheckCode As Integer
Public Property CheckNumber As String
Public Property ExportDate As Date?
Public Property AttainDate As Date?
Public Property ReceiveDate As Date?
Public Property SourcePersonCode As String
Public Property DestPersonCode As String
Public Property IsPayable As Boolean
Public Property IsCleared As Boolean
Public Property IsInProcess As Boolean
Public Property IsReturned As Boolean
Public Property IsVoid As Boolean
Public Property Amount As Double
Public Property BankCode As String
Public Property AccountNumber As String
Public Property BankName As String
Public Property Comment As String
Public Property SanadCode As Integer
Public Property SanadCode2 As Integer
Public ReadOnly Property Key As String
Get
Return "CHK:" & CheckCode.ToString()
End Get
End Property
Public ReadOnly Property IssueDate As Date
Get
If ExportDate.HasValue Then Return ExportDate.Value.Date
If ReceiveDate.HasValue Then Return ReceiveDate.Value.Date
If AttainDate.HasValue Then Return AttainDate.Value.Date
Return Date.Today
End Get
End Property
Public ReadOnly Property DueDate As Date
Get
If AttainDate.HasValue Then Return AttainDate.Value.Date
If ReceiveDate.HasValue Then Return ReceiveDate.Value.Date
Return IssueDate
End Get
End Property
Public ReadOnly Property ClearDate As Date
Get
If AttainDate.HasValue Then Return AttainDate.Value.Date
If ReceiveDate.HasValue Then Return ReceiveDate.Value.Date
Return IssueDate
End Get
End Property
End Class
Friend Class HolooReceiptLikeSanad
Public Property SanadCode As Integer
Public Property SanadDate As Date
Public Property Comment As String
Public Property IsReceipt As Boolean
Public Property PersonMoien As String
Public Property PersonAmount As Double
Public Property CounterCol As String
Public Property CounterMoien As String
Public Property CounterTafzili As String
Public Property CounterAmount As Double
Public Property CounterName As String
Public ReadOnly Property Key As String
Get
Return "SND20:" & SanadCode.ToString()
End Get
End Property
End Class
Friend Class HolooExpenseIncomeSanad
Public Property SanadCode As Integer
Public Property SanadDate As Date
Public Property Comment As String
Public Property IsIncome As Boolean
Public Property ItemLines As New List(Of HolooEiItemLine)
Public Property CounterpartyLines As New List(Of HolooEiCounterLine)
Public ReadOnly Property Key As String
Get
Return If(IsIncome, "INC:", "EXP:") & SanadCode.ToString()
End Get
End Property
End Class
Friend Class HolooEiItemLine
Public Property MoienCode As String
Public Property Name As String
Public Property Amount As Double
End Class
Friend Class HolooEiCounterLine
Public Property ColCode As String
Public Property MoienCode As String
Public Property TafziliCode As String
Public Property Name As String
Public Property Amount As Double
End Class
Friend Class HolooManualJournal
Public Property SanadCode As Integer
Public Property SanadDate As Date
Public Property SanadType As Integer
Public Property Comment As String
Public Property Lines As New List(Of HolooManualJournalLine)
''' <summary>اگر پر باشد، سند در مبدأ قابل انتقال نیست و باید Failed شود (نه silent skip).</summary>
Public Property SkipReason As String
Public ReadOnly Property Key As String
Get
Return "JRN:" & SanadCode.ToString()
End Get
End Property
End Class
Friend Class HolooManualJournalLine
Public Property ColCode As String
Public Property MoienCode As String
Public Property TafziliCode As String
Public Property SarfaslName As String
Public Property Debit As Double
Public Property Credit As Double
End Class
Friend Class HolooDocumentReader
Public Function ReadInvoicesInRange(
settings As SqlConnectionSettings,
startDate As Date,
endDateInclusive As Date,
Optional facTypes As IEnumerable(Of String) = Nothing
) As List(Of HolooInvoiceHeader)
Dim types = If(facTypes, {"F", "K", "Y", "X", "Z"}).ToList()
Dim list As New List(Of HolooInvoiceHeader)
Using conn = Open(settings)
Dim typeParams = String.Join(",", types.Select(Function(t, i) "@t" & i.ToString()))
Dim sql =
"SELECT Fac_Code, Fac_Type, C_Code, Fac_Date, ISNULL(Sum_Price,0), ISNULL(Takhfif,0), " &
"ISNULL(Sum_Levy,0), ISNULL(Sum_Scot,0), ISNULL(FNaghd,0), ISNULL(FCheck,0), ISNULL(FNesieh,0), " &
"ISNULL(Card,0), ISNULL(FHaval,0), ISNULL(Fac_Comment,''), ISNULL(Sanad_Code,0) " &
"FROM FACTURE WHERE ISNULL([Delete],0)=0 AND Fac_Date>=@s AND Fac_Date<@e " &
"AND Fac_Type IN (" & typeParams & ") ORDER BY Fac_Date, Fac_Type, Fac_Code;"
Using cmd As New SqlCommand(sql, conn)
cmd.CommandTimeout = 300
cmd.Parameters.AddWithValue("@s", startDate)
cmd.Parameters.AddWithValue("@e", endDateInclusive.AddDays(1))
For i = 0 To types.Count - 1
cmd.Parameters.AddWithValue("@t" & i.ToString(), types(i))
Next
Using r = cmd.ExecuteReader()
While r.Read()
list.Add(New HolooInvoiceHeader With {
.FacCode = SafeStr(r, 0),
.FacType = SafeStr(r, 1),
.CustomerCode = SafeStr(r, 2),
.FacDate = Convert.ToDateTime(r.GetValue(3)).Date,
.SumPrice = SafeDbl(r, 4),
.Takhfif = SafeDbl(r, 5),
.SumLevy = SafeDbl(r, 6),
.SumScot = SafeDbl(r, 7),
.FNaghd = SafeDbl(r, 8),
.FCheck = SafeDbl(r, 9),
.FNesieh = SafeDbl(r, 10),
.Card = SafeDbl(r, 11),
.FHaval = SafeDbl(r, 12),
.Comment = SafeStr(r, 13),
.SanadCode = SafeInt(r, 14)
})
End While
End Using
End Using
If list.Count = 0 Then Return list
' بارگذاری خطوط — برای حجم بالا per-invoice سنگین است؛ یک کوئری بازه‌ای
Dim lineSql =
"SELECT a.Fac_Code, a.Fac_Type, a.A_Code, ISNULL(a.Few_Article,0), ISNULL(a.Price_BS,0), " &
"ISNULL(a.TakhfifSatriR,0), ISNULL(a.Levy,0), ISNULL(a.Scot,0), ISNULL(a.Money_Code,0), ISNULL(a.FacArtic_Comment,''), " &
"ISNULL(a.Price_Dollari,0) " &
"FROM FACTART a " &
"INNER JOIN FACTURE f ON f.Fac_Code=a.Fac_Code AND f.Fac_Type=a.Fac_Type " &
"WHERE ISNULL(f.[Delete],0)=0 AND f.Fac_Date>=@s AND f.Fac_Date<@e " &
"AND f.Fac_Type IN (" & typeParams & ") " &
"ORDER BY a.Fac_Type, a.Fac_Code, a.A_Index;"
Dim byKey As New Dictionary(Of String, HolooInvoiceHeader)(StringComparer.Ordinal)
For Each h In list
byKey(h.Key) = h
Next
Using cmd As New SqlCommand(lineSql, conn)
cmd.CommandTimeout = 600
cmd.Parameters.AddWithValue("@s", startDate)
cmd.Parameters.AddWithValue("@e", endDateInclusive.AddDays(1))
For i = 0 To types.Count - 1
cmd.Parameters.AddWithValue("@t" & i.ToString(), types(i))
Next
Using r = cmd.ExecuteReader()
While r.Read()
Dim key = SafeStr(r, 1) & ":" & SafeStr(r, 0)
Dim header As HolooInvoiceHeader = Nothing
If Not byKey.TryGetValue(key, header) Then Continue While
header.Lines.Add(New HolooInvoiceLine With {
.ArticleCode = SafeStr(r, 2),
.Quantity = SafeDbl(r, 3),
.UnitPrice = SafeDbl(r, 4),
.LineDiscount = SafeDbl(r, 5),
.Levy = SafeDbl(r, 6),
.Scot = SafeDbl(r, 7),
.MoneyCode = SafeInt(r, 8),
.Comment = SafeStr(r, 9),
.ForeignUnitPrice = SafeDbl(r, 10)
})
End While
End Using
End Using
End Using
Return list
End Function
''' <summary>
''' از سند اتوماتیک فاکتور، حساب صندوق/بانک تسویه را برای نگاشت دقیق استخراج می‌کند.
''' </summary>
Public Function ReadSettlementHintsForSanads(
settings As SqlConnectionSettings,
sanadCodes As IEnumerable(Of Integer)
) As Dictionary(Of Integer, HolooInvoiceSettlementHint)
Dim map As New Dictionary(Of Integer, HolooInvoiceSettlementHint)
Dim codes = sanadCodes.Where(Function(c) c > 0).Distinct().ToList()
If codes.Count = 0 Then Return map
Using conn = Open(settings)
Const chunk As Integer = 400
Dim offset = 0
While offset < codes.Count
Dim take = Math.Min(chunk, codes.Count - offset)
Dim part = codes.GetRange(offset, take)
Dim parms = String.Join(",", part.Select(Function(c, i) "@p" & i.ToString()))
Dim sql =
"SELECT l.Sanad_Code, l.Col_Code, ISNULL(l.Moien_Code,''), ISNULL(l.Tafzili_Code,''), " &
"ISNULL(s.Sarfasl_Name,''), ISNULL(l.Bed,0), ISNULL(l.Bes,0) " &
"FROM SND_LIST l LEFT JOIN SARFASL s ON s.Col_Code=l.Col_Code AND ISNULL(s.Moien_Code,'')=ISNULL(l.Moien_Code,'') " &
" AND ISNULL(s.Tafzili_Code,'')=ISNULL(l.Tafzili_Code,'') " &
"WHERE l.Sanad_Code IN (" & parms & ") AND l.Col_Code IN ('101','102');"
Using cmd As New SqlCommand(sql, conn)
cmd.CommandTimeout = 180
For i = 0 To part.Count - 1
cmd.Parameters.AddWithValue("@p" & i.ToString(), part(i))
Next
Using r = cmd.ExecuteReader()
While r.Read()
Dim sc = SafeInt(r, 0)
Dim hint As HolooInvoiceSettlementHint = Nothing
If Not map.TryGetValue(sc, hint) Then
hint = New HolooInvoiceSettlementHint With {.SanadCode = sc}
map(sc) = hint
End If
Dim col = SafeStr(r, 1)
Dim bed = SafeDbl(r, 5)
Dim bes = SafeDbl(r, 6)
' در فروش: صندوق/بانک بدهکار؛ در خرید: بستانکار
If col = "101" AndAlso (bed > 0 OrElse bes > 0) Then
If String.IsNullOrWhiteSpace(hint.CashMoien) OrElse bed >= bes Then
hint.CashCol = col
hint.CashMoien = SafeStr(r, 2)
hint.CashTafzili = SafeStr(r, 3)
hint.CashName = SafeStr(r, 4)
End If
ElseIf col = "102" AndAlso (bed > 0 OrElse bes > 0) Then
If String.IsNullOrWhiteSpace(hint.BankMoien) OrElse bed >= bes Then
hint.BankCol = col
hint.BankMoien = SafeStr(r, 2)
hint.BankTafzili = SafeStr(r, 3)
hint.BankName = SafeStr(r, 4)
End If
End If
End While
End Using
End Using
offset += take
End While
End Using
Return map
End Function
Public Function FindOpeningSanadCode(settings As SqlConnectionSettings) As Integer
Using conn = Open(settings)
Using cmd As New SqlCommand(
"SELECT TOP 1 Sanad_Code FROM SANAD WHERE ISNULL([Delete],0)=0 AND (" &
" Comment LIKE N'%افتتاح%' OR Sanad_Code=1) ORDER BY Sanad_Code;", conn)
Dim o = cmd.ExecuteScalar()
If o Is Nothing OrElse o Is DBNull.Value Then Return 0
Return Convert.ToInt32(o)
End Using
End Using
End Function
Public Function ReadOpeningLines(settings As SqlConnectionSettings, sanadCode As Integer) As List(Of HolooOpeningLine)
Dim list As New List(Of HolooOpeningLine)
If sanadCode <= 0 Then Return list
Using conn = Open(settings)
Dim sql =
"SELECT l.Col_Code, ISNULL(l.Moien_Code,''), ISNULL(l.Tafzili_Code,''), ISNULL(s.Sarfasl_Name,''), " &
"ISNULL(l.Bed,0), ISNULL(l.Bes,0), ISNULL(l.Comment_Line,'') " &
"FROM SND_LIST l " &
"LEFT JOIN SARFASL s ON s.Col_Code=l.Col_Code AND ISNULL(s.Moien_Code,'')=ISNULL(l.Moien_Code,'') " &
" AND ISNULL(s.Tafzili_Code,'')=ISNULL(l.Tafzili_Code,'') " &
"WHERE l.Sanad_Code=@c ORDER BY l.[Index];"
Using cmd As New SqlCommand(sql, conn)
cmd.CommandTimeout = 180
cmd.Parameters.AddWithValue("@c", sanadCode)
Using r = cmd.ExecuteReader()
While r.Read()
list.Add(New HolooOpeningLine With {
.ColCode = SafeStr(r, 0),
.MoienCode = SafeStr(r, 1),
.TafziliCode = SafeStr(r, 2),
.SarfaslName = SafeStr(r, 3),
.Debit = SafeDbl(r, 4),
.Credit = SafeDbl(r, 5),
.Comment = SafeStr(r, 6)
})
End While
End Using
End Using
End Using
Return list
End Function
Public Function ReadReceiptLikeSanadsInRange(
settings As SqlConnectionSettings,
startDate As Date,
endDateInclusive As Date
) As List(Of HolooReceiptLikeSanad)
Dim list As New List(Of HolooReceiptLikeSanad)
Using conn = Open(settings)
Dim sql =
"SELECT s.Sanad_Code, s.Sanad_Date, ISNULL(s.Comment,'') " &
"FROM SANAD s WHERE ISNULL(s.[Delete],0)=0 AND ISNULL(s.SaveFromFacture,0)=0 " &
"AND s.Sanad_Type=20 AND s.Sanad_Date>=@s AND s.Sanad_Date<@e " &
"AND NOT EXISTS (SELECT 1 FROM SND_LIST x WHERE x.Sanad_Code=s.Sanad_Code AND x.Col_Code IN ('601','702')) " &
"ORDER BY s.Sanad_Date, s.Sanad_Code;"
Dim headers As New List(Of Tuple(Of Integer, Date, String))
Using cmd As New SqlCommand(sql, conn)
cmd.CommandTimeout = 180
cmd.Parameters.AddWithValue("@s", startDate)
cmd.Parameters.AddWithValue("@e", endDateInclusive.AddDays(1))
Using r = cmd.ExecuteReader()
While r.Read()
headers.Add(Tuple.Create(SafeInt(r, 0), Convert.ToDateTime(r.GetValue(1)).Date, SafeStr(r, 2)))
End While
End Using
End Using
For Each h In headers
Dim lines As New List(Of Tuple(Of String, String, String, Double, Double, String))
Using cmd As New SqlCommand(
"SELECT l.Col_Code, ISNULL(l.Moien_Code,''), ISNULL(l.Tafzili_Code,''), ISNULL(l.Bed,0), ISNULL(l.Bes,0), ISNULL(s.Sarfasl_Name,'') " &
"FROM SND_LIST l LEFT JOIN SARFASL s ON s.Col_Code=l.Col_Code AND ISNULL(s.Moien_Code,'')=ISNULL(l.Moien_Code,'') " &
" AND ISNULL(s.Tafzili_Code,'')=ISNULL(l.Tafzili_Code,'') WHERE l.Sanad_Code=@c ORDER BY l.[Index];", conn)
cmd.Parameters.AddWithValue("@c", h.Item1)
Using r = cmd.ExecuteReader()
While r.Read()
lines.Add(Tuple.Create(SafeStr(r, 0), SafeStr(r, 1), SafeStr(r, 2), SafeDbl(r, 3), SafeDbl(r, 4), SafeStr(r, 5)))
End While
End Using
End Using
Dim personLine = lines.FirstOrDefault(Function(x) x.Item1 = "103")
Dim cashBank = lines.FirstOrDefault(Function(x) x.Item1 = "101" OrElse x.Item1 = "102")
If personLine Is Nothing OrElse cashBank Is Nothing Then Continue For
Dim isReceipt = (cashBank.Item4 > 0 AndAlso personLine.Item5 > 0)
Dim isPayment = (cashBank.Item5 > 0 AndAlso personLine.Item4 > 0)
If Not isReceipt AndAlso Not isPayment Then Continue For
Dim amount = If(isReceipt, personLine.Item5, personLine.Item4)
If amount <= 0 Then amount = If(isReceipt, cashBank.Item4, cashBank.Item5)
list.Add(New HolooReceiptLikeSanad With {
.SanadCode = h.Item1,
.SanadDate = h.Item2,
.Comment = h.Item3,
.IsReceipt = isReceipt,
.PersonMoien = personLine.Item2,
.PersonAmount = amount,
.CounterCol = cashBank.Item1,
.CounterMoien = cashBank.Item2,
.CounterTafzili = cashBank.Item3,
.CounterAmount = amount,
.CounterName = cashBank.Item6
})
Next
End Using
Return list
End Function
Public Function ReadChecksInRange(
settings As SqlConnectionSettings,
startDate As Date,
endDateInclusive As Date
) As List(Of HolooCheckRow)
Dim list As New List(Of HolooCheckRow)
Using conn = Open(settings)
Dim sql =
"SELECT c.Check_Code, ISNULL(c.Check_Number,''), c.Export_Date, c.Attain_Date, c.Receive_Date, " &
"ISNULL(c.C_Code_Source,''), ISNULL(c.C_Code_Destination,''), ISNULL(c.Daryaft_Pardakht,0), " &
"ISNULL(c.Vosool,0), ISNULL(c.DarJaryan,0), ISNULL(c.Bargashty,0), ISNULL(c.IsEbtal,0), " &
"ISNULL(c.Cust,0), ISNULL(c.Bank_Code,''), ISNULL(c.Account_Number,''), ISNULL(c.Comm,''), " &
"ISNULL(c.Sanad_Code,0), ISNULL(c.Sanad_Code2,0), ISNULL(b.Bank_Name,'') " &
"FROM [Check] c LEFT JOIN NEWBANK b ON b.Bank_Code=c.Bank_Code " &
"WHERE ISNULL(c.[Delete],0)=0 AND (" &
" (c.Export_Date>=@s AND c.Export_Date<@e) OR (c.Receive_Date>=@s AND c.Receive_Date<@e) OR (c.Attain_Date>=@s AND c.Attain_Date<@e)" &
") ORDER BY c.Check_Code;"
Using cmd As New SqlCommand(sql, conn)
cmd.CommandTimeout = 180
cmd.Parameters.AddWithValue("@s", startDate)
cmd.Parameters.AddWithValue("@e", endDateInclusive.AddDays(1))
Using r = cmd.ExecuteReader()
While r.Read()
list.Add(New HolooCheckRow With {
.CheckCode = SafeInt(r, 0),
.CheckNumber = SafeStr(r, 1),
.ExportDate = SafeDate(r, 2),
.AttainDate = SafeDate(r, 3),
.ReceiveDate = SafeDate(r, 4),
.SourcePersonCode = SafeStr(r, 5),
.DestPersonCode = SafeStr(r, 6),
.IsPayable = SafeBool(r, 7),
.IsCleared = SafeBool(r, 8),
.IsInProcess = SafeBool(r, 9),
.IsReturned = SafeBool(r, 10),
.IsVoid = SafeBool(r, 11),
.Amount = SafeDbl(r, 12),
.BankCode = SafeStr(r, 13),
.AccountNumber = SafeStr(r, 14),
.Comment = SafeStr(r, 15),
.SanadCode = SafeInt(r, 16),
.SanadCode2 = SafeInt(r, 17),
.BankName = SafeStr(r, 18)
})
End While
End Using
End Using
End Using
Return list
End Function
Public Function ReadExpenseIncomeSanadsInRange(
settings As SqlConnectionSettings,
startDate As Date,
endDateInclusive As Date,
asIncome As Boolean
) As List(Of HolooExpenseIncomeSanad)
Dim list As New List(Of HolooExpenseIncomeSanad)
Dim targetCol = If(asIncome, "702", "601")
Using conn = Open(settings)
Dim sql =
"SELECT s.Sanad_Code, s.Sanad_Date, ISNULL(s.Comment,'') FROM SANAD s " &
"WHERE ISNULL(s.[Delete],0)=0 AND ISNULL(s.SaveFromFacture,0)=0 " &
"AND s.Sanad_Date>=@s AND s.Sanad_Date<@e " &
"AND EXISTS (SELECT 1 FROM SND_LIST x WHERE x.Sanad_Code=s.Sanad_Code AND x.Col_Code=@col) " &
"AND NOT EXISTS (SELECT 1 FROM FACTURE f WHERE f.Sanad_Code=s.Sanad_Code AND ISNULL(f.[Delete],0)=0) " &
"AND NOT EXISTS (SELECT 1 FROM [Check] c WHERE c.Sanad_Code=s.Sanad_Code OR c.Sanad_Code2=s.Sanad_Code) " &
"ORDER BY s.Sanad_Date, s.Sanad_Code;"
Dim headers As New List(Of Tuple(Of Integer, Date, String))
Using cmd As New SqlCommand(sql, conn)
cmd.CommandTimeout = 180
cmd.Parameters.AddWithValue("@s", startDate)
cmd.Parameters.AddWithValue("@e", endDateInclusive.AddDays(1))
cmd.Parameters.AddWithValue("@col", targetCol)
Using r = cmd.ExecuteReader()
While r.Read()
headers.Add(Tuple.Create(SafeInt(r, 0), Convert.ToDateTime(r.GetValue(1)).Date, SafeStr(r, 2)))
End While
End Using
End Using
For Each h In headers
Dim lines As New List(Of Tuple(Of String, String, String, Double, Double, String))
Using cmd As New SqlCommand(
"SELECT l.Col_Code, ISNULL(l.Moien_Code,''), ISNULL(l.Tafzili_Code,''), ISNULL(l.Bed,0), ISNULL(l.Bes,0), ISNULL(s.Sarfasl_Name,'') " &
"FROM SND_LIST l LEFT JOIN SARFASL s ON s.Col_Code=l.Col_Code AND ISNULL(s.Moien_Code,'')=ISNULL(l.Moien_Code,'') " &
" AND ISNULL(s.Tafzili_Code,'')=ISNULL(l.Tafzili_Code,'') WHERE l.Sanad_Code=@c ORDER BY l.[Index];", conn)
cmd.Parameters.AddWithValue("@c", h.Item1)
Using r = cmd.ExecuteReader()
While r.Read()
lines.Add(Tuple.Create(SafeStr(r, 0), SafeStr(r, 1), SafeStr(r, 2), SafeDbl(r, 3), SafeDbl(r, 4), SafeStr(r, 5)))
End While
End Using
End Using
Dim doc As New HolooExpenseIncomeSanad With {
.SanadCode = h.Item1,
.SanadDate = h.Item2,
.Comment = h.Item3,
.IsIncome = asIncome
}
If asIncome Then
For Each ln In lines
If ln.Item1 = "702" AndAlso ln.Item5 > 0 Then
doc.ItemLines.Add(New HolooEiItemLine With {.MoienCode = ln.Item2, .Name = ln.Item6, .Amount = ln.Item5})
ElseIf (ln.Item1 = "101" OrElse ln.Item1 = "102" OrElse ln.Item1 = "103" OrElse ln.Item1 = "401") AndAlso ln.Item4 > 0 Then
doc.CounterpartyLines.Add(New HolooEiCounterLine With {
.ColCode = ln.Item1, .MoienCode = ln.Item2, .TafziliCode = ln.Item3, .Name = ln.Item6, .Amount = ln.Item4
})
End If
Next
Else
For Each ln In lines
If ln.Item1 = "601" AndAlso ln.Item4 > 0 Then
doc.ItemLines.Add(New HolooEiItemLine With {.MoienCode = ln.Item2, .Name = ln.Item6, .Amount = ln.Item4})
ElseIf (ln.Item1 = "101" OrElse ln.Item1 = "102" OrElse ln.Item1 = "103" OrElse ln.Item1 = "401") AndAlso ln.Item5 > 0 Then
doc.CounterpartyLines.Add(New HolooEiCounterLine With {
.ColCode = ln.Item1, .MoienCode = ln.Item2, .TafziliCode = ln.Item3, .Name = ln.Item6, .Amount = ln.Item5
})
End If
Next
End If
If doc.ItemLines.Count = 0 OrElse doc.CounterpartyLines.Count = 0 Then Continue For
Dim itemSum = doc.ItemLines.Sum(Function(x) x.Amount)
Dim cpSum = doc.CounterpartyLines.Sum(Function(x) x.Amount)
If Math.Abs(itemSum - cpSum) > 1.0 Then Continue For ' نامتوازن برای API هزینه — به fallback دستی می‌رود
list.Add(doc)
Next
End Using
Return list
End Function
''' <summary>
''' اسناد دارای 601/702 که ساختار ساده هزینه/درآمد متوازن ندارند (مثلاً ترکیبی Type20)
''' ولی خود سند تراز است — به‌صورت MANUAL با برچسب EXPENSE_INCOME_FALLBACK تا از پارتیشن خارج نشوند.
''' رسید Type20 خالص (بدون 601/702) اینجا نیست؛ هزینه متوازن هم نیست.
''' </summary>
Public Function ReadExpenseLikeFallbackManualsInRange(
settings As SqlConnectionSettings,
startDate As Date,
endDateInclusive As Date
) As List(Of HolooManualJournal)
Dim list As New List(Of HolooManualJournal)
Dim openingCode = FindOpeningSanadCode(settings)
Dim structuredKeys As New HashSet(Of Integer)()
For Each d In ReadExpenseIncomeSanadsInRange(settings, startDate, endDateInclusive, False)
structuredKeys.Add(d.SanadCode)
Next
For Each d In ReadExpenseIncomeSanadsInRange(settings, startDate, endDateInclusive, True)
structuredKeys.Add(d.SanadCode)
Next
Using conn = Open(settings)
Dim sql =
"SELECT s.Sanad_Code, s.Sanad_Date, ISNULL(s.Sanad_Type,0), ISNULL(s.Comment,'') " &
"FROM SANAD s WHERE ISNULL(s.[Delete],0)=0 AND ISNULL(s.SaveFromFacture,0)=0 " &
"AND s.Sanad_Code<>@open AND s.Sanad_Date>=@s AND s.Sanad_Date<@e " &
"AND ISNULL(s.Sanad_Type,0)<>5 " &
"AND EXISTS (SELECT 1 FROM SND_LIST x WHERE x.Sanad_Code=s.Sanad_Code AND x.Col_Code IN ('601','702')) " &
"AND NOT EXISTS (SELECT 1 FROM FACTURE f WHERE f.Sanad_Code=s.Sanad_Code AND ISNULL(f.[Delete],0)=0) " &
"AND NOT EXISTS (SELECT 1 FROM [Check] c WHERE c.Sanad_Code=s.Sanad_Code OR c.Sanad_Code2=s.Sanad_Code) " &
"ORDER BY s.Sanad_Date, s.Sanad_Code;"
Dim headers As New List(Of Tuple(Of Integer, Date, Integer, String))
Using cmd As New SqlCommand(sql, conn)
cmd.CommandTimeout = 180
cmd.Parameters.AddWithValue("@open", openingCode)
cmd.Parameters.AddWithValue("@s", startDate)
cmd.Parameters.AddWithValue("@e", endDateInclusive.AddDays(1))
Using r = cmd.ExecuteReader()
While r.Read()
headers.Add(Tuple.Create(SafeInt(r, 0), Convert.ToDateTime(r.GetValue(1)).Date, SafeInt(r, 2), SafeStr(r, 3)))
End While
End Using
End Using
For Each h In headers
If structuredKeys.Contains(h.Item1) Then Continue For
Dim doc As New HolooManualJournal With {
.SanadCode = h.Item1,
.SanadDate = h.Item2,
.SanadType = h.Item3,
.Comment = If(String.IsNullOrWhiteSpace(h.Item4), "", h.Item4) & " [EI-FALLBACK]"
}
Dim bedSum As Double = 0
Dim besSum As Double = 0
Using cmd As New SqlCommand(
"SELECT l.Col_Code, ISNULL(l.Moien_Code,''), ISNULL(l.Tafzili_Code,''), ISNULL(s.Sarfasl_Name,''), ISNULL(l.Bed,0), ISNULL(l.Bes,0) " &
"FROM SND_LIST l LEFT JOIN SARFASL s ON s.Col_Code=l.Col_Code AND ISNULL(s.Moien_Code,'')=ISNULL(l.Moien_Code,'') " &
" AND ISNULL(s.Tafzili_Code,'')=ISNULL(l.Tafzili_Code,'') WHERE l.Sanad_Code=@c ORDER BY l.[Index];", conn)
cmd.Parameters.AddWithValue("@c", h.Item1)
Using r = cmd.ExecuteReader()
While r.Read()
Dim d = SafeDbl(r, 4)
Dim c = SafeDbl(r, 5)
If d <= 0 AndAlso c <= 0 Then Continue While
bedSum += d
besSum += c
doc.Lines.Add(New HolooManualJournalLine With {
.ColCode = SafeStr(r, 0),
.MoienCode = SafeStr(r, 1),
.TafziliCode = SafeStr(r, 2),
.SarfaslName = SafeStr(r, 3),
.Debit = d,
.Credit = c
})
End While
End Using
End Using
If doc.Lines.Count < 2 Then
doc.SkipReason = "کمتر از دو خط مؤثر در مبدأ (EI-fallback)"
list.Add(doc)
Continue For
End If
If Math.Abs(bedSum - besSum) > 1.0 Then
doc.SkipReason = "نامتوازن در مبدأ (EI-fallback)"
list.Add(doc)
Continue For
End If
list.Add(doc)
Next
End Using
Return list
End Function
''' <summary>
''' اسناد دستی/عمومی باقی‌مانده (غیر فاکتور، غیر Type5/20، غیر لینک چک، غیر هزینه/درآمد).
''' هر سندی که ردیف FACTURE فعال دارد از این ماژول خارج است تا GL فاکتور دوبل نشود
''' (حتی اگر SaveFromFacture=0 باشد؛ نمونه Holoo1: صدها Type13/14 متصل به F/K).
''' </summary>
Public Function ReadManualJournalsInRange(
settings As SqlConnectionSettings,
startDate As Date,
endDateInclusive As Date
) As List(Of HolooManualJournal)
Dim list As New List(Of HolooManualJournal)
Dim openingCode = FindOpeningSanadCode(settings)
Using conn = Open(settings)
Dim sql =
"SELECT s.Sanad_Code, s.Sanad_Date, ISNULL(s.Sanad_Type,0), ISNULL(s.Comment,'') " &
"FROM SANAD s WHERE ISNULL(s.[Delete],0)=0 AND ISNULL(s.SaveFromFacture,0)=0 " &
"AND s.Sanad_Code<>@open AND s.Sanad_Date>=@s AND s.Sanad_Date<@e " &
"AND ISNULL(s.Sanad_Type,0) NOT IN (5,20) " &
"AND NOT EXISTS (SELECT 1 FROM FACTURE f WHERE f.Sanad_Code=s.Sanad_Code AND ISNULL(f.[Delete],0)=0) " &
"AND NOT EXISTS (SELECT 1 FROM [Check] c WHERE c.Sanad_Code=s.Sanad_Code OR c.Sanad_Code2=s.Sanad_Code) " &
"AND NOT EXISTS (SELECT 1 FROM SND_LIST x WHERE x.Sanad_Code=s.Sanad_Code AND x.Col_Code IN ('601','702')) " &
"ORDER BY s.Sanad_Date, s.Sanad_Code;"
Dim headers As New List(Of Tuple(Of Integer, Date, Integer, String))
Using cmd As New SqlCommand(sql, conn)
cmd.CommandTimeout = 180
cmd.Parameters.AddWithValue("@open", openingCode)
cmd.Parameters.AddWithValue("@s", startDate)
cmd.Parameters.AddWithValue("@e", endDateInclusive.AddDays(1))
Using r = cmd.ExecuteReader()
While r.Read()
headers.Add(Tuple.Create(SafeInt(r, 0), Convert.ToDateTime(r.GetValue(1)).Date, SafeInt(r, 2), SafeStr(r, 3)))
End While
End Using
End Using
For Each h In headers
Dim doc As New HolooManualJournal With {
.SanadCode = h.Item1,
.SanadDate = h.Item2,
.SanadType = h.Item3,
.Comment = h.Item4
}
Dim bedSum As Double = 0
Dim besSum As Double = 0
Using cmd As New SqlCommand(
"SELECT l.Col_Code, ISNULL(l.Moien_Code,''), ISNULL(l.Tafzili_Code,''), ISNULL(s.Sarfasl_Name,''), ISNULL(l.Bed,0), ISNULL(l.Bes,0) " &
"FROM SND_LIST l LEFT JOIN SARFASL s ON s.Col_Code=l.Col_Code AND ISNULL(s.Moien_Code,'')=ISNULL(l.Moien_Code,'') " &
" AND ISNULL(s.Tafzili_Code,'')=ISNULL(l.Tafzili_Code,'') WHERE l.Sanad_Code=@c ORDER BY l.[Index];", conn)
cmd.Parameters.AddWithValue("@c", h.Item1)
Using r = cmd.ExecuteReader()
While r.Read()
Dim d = SafeDbl(r, 4)
Dim c = SafeDbl(r, 5)
If d <= 0 AndAlso c <= 0 Then Continue While
bedSum += d
besSum += c
doc.Lines.Add(New HolooManualJournalLine With {
.ColCode = SafeStr(r, 0),
.MoienCode = SafeStr(r, 1),
.TafziliCode = SafeStr(r, 2),
.SarfaslName = SafeStr(r, 3),
.Debit = d,
.Credit = c
})
End While
End Using
End Using
If doc.Lines.Count < 2 Then
doc.SkipReason = "کمتر از دو خط مؤثر در مبدأ"
list.Add(doc)
Continue For
End If
If Math.Abs(bedSum - besSum) > 1.0 Then
doc.SkipReason = "نامتوازن در مبدأ (|Bed-Bes|>1)"
list.Add(doc)
Continue For
End If
list.Add(doc)
Next
End Using
' پارتیشن: Type20 ترکیبی 601/702 که API هزینه نمی‌پذیرد → دستی (بدون دوبل با EI متوازن)
For Each fb In ReadExpenseLikeFallbackManualsInRange(settings, startDate, endDateInclusive)
If list.Any(Function(x) x.SanadCode = fb.SanadCode) Then Continue For
list.Add(fb)
Next
Return list
End Function
Public Function ReadProductOpeningQuantities(settings As SqlConnectionSettings) As List(Of Tuple(Of String, Double, Double, Integer?))
Dim list As New List(Of Tuple(Of String, Double, Double, Integer?))
Using conn = Open(settings)
Using cmd As New SqlCommand(
"SELECT A_Code, ISNULL(First_exist,0), ISNULL(NULLIF(FirstBuy_Price,0), ISNULL(Buy_Price,0)), Type_Anbar_C " &
"FROM ARTICLE WHERE ISNULL([Delete],0)=0 AND ISNULL(First_exist,0)>0;", conn)
cmd.CommandTimeout = 180
Using r = cmd.ExecuteReader()
While r.Read()
Dim code = SafeStr(r, 0)
Dim qty = SafeDbl(r, 1)
Dim cost = SafeDbl(r, 2)
Dim wh As Integer? = Nothing
If Not r.IsDBNull(3) Then
Try
wh = Convert.ToInt32(r.GetValue(3))
Catch
wh = Nothing
End Try
End If
If code.Length > 0 AndAlso qty > 0 Then
list.Add(Tuple.Create(code, qty, cost, wh))
End If
End While
End Using
End Using
End Using
Return list
End Function
Public Shared Function MapFacTypeToInvoiceType(facType As String) As String
Select Case (If(facType, "")).Trim().ToUpperInvariant()
Case "F" : Return "invoice_sales"
Case "K" : Return "invoice_purchase"
Case "Y" : Return "invoice_sales_return"
Case "X" : Return "invoice_purchase_return"
Case "Z" : Return "invoice_direct_consumption" ' مصرف مستقیم کالا (Dr هزینه / Cr موجودی-خرید)
Case Else : Return Nothing
End Select
End Function
Public Shared Function InvoiceRequiresPerson(facType As String) As Boolean
Select Case (If(facType, "")).Trim().ToUpperInvariant()
Case "Z" : Return False
Case Else : Return True
End Select
End Function
Private Shared Function Open(settings As SqlConnectionSettings) As SqlConnection
Dim conn As New SqlConnection(settings.BuildConnectionString(settings.Database))
conn.Open()
Return conn
End Function
Private Shared Function SafeStr(r As SqlDataReader, i As Integer) As String
If r.IsDBNull(i) Then Return ""
Return Convert.ToString(r.GetValue(i)).Trim()
End Function
Private Shared Function SafeInt(r As SqlDataReader, i As Integer) As Integer
If r.IsDBNull(i) Then Return 0
Return Convert.ToInt32(r.GetValue(i))
End Function
Private Shared Function SafeDbl(r As SqlDataReader, i As Integer) As Double
If r.IsDBNull(i) Then Return 0
Return Convert.ToDouble(r.GetValue(i))
End Function
Private Shared Function SafeBool(r As SqlDataReader, i As Integer) As Boolean
If r.IsDBNull(i) Then Return False
Dim v = r.GetValue(i)
If TypeOf v Is Boolean Then Return CBool(v)
Return Convert.ToInt32(v) <> 0
End Function
Private Shared Function SafeDate(r As SqlDataReader, i As Integer) As Date?
If r.IsDBNull(i) Then Return Nothing
Try
Return Convert.ToDateTime(r.GetValue(i)).Date
Catch
Return Nothing
End Try
End Function
End Class