کار با موجودیت صورتحساب

مهمترین موجودیت تدبیر مؤدیان صورتحساب است که در قالب یک موجودیت مفصل به نام IntamediaInvoice تعریف شده است. تعریف کامل این موجودیت به همراه موجودیت‌هایی که به آن وابستگی دارد را می‌توانید در این پوشه مشاهده کنید. تعداد زیادی از اعضای این موجودیت در شرایط خاص پر می‌شوند و در شرایط عمومی‌تر با null پر می‌شوند.

در ادامه برای سادگی با شکلهای خلاصه‌ای از این موجودیت کار می‌کنیم:

/// <summary>
/// صورتحساب سامانهٔ مؤدیان
/// </summary>
public class IntamediaInvoice
{
    /// <summary>
    /// Id
    /// </summary>
    public Guid Id { get; set; }

    /// <summary>
    /// Workspace Id
    /// </summary>
    public Guid WorkspaceId { get; set; }
    
    /// <summary>
    /// وضعیت
    /// </summary>
    public IntamediaInvoiceStatus InvoiceStatus { get; set; }    

// اعضای دیگر کلاس برای سادگی در این کد نمونه نیامده است
}

هر صورتحساب قطعاً متعلق به یک شرکت است است که شناسهٔ آن در عضو WorkspaceId نگهداری می‌شود. در هنگام دریافت فهرست صورتحساب‌ها نیز باید شناسهٔ شرکت را مشخص کنید.

وضعیت صورتحساب در عضو InvoiceStatus نگهداری می‌شود. کد زیر مشخصات وضعیت‌های صورتحساب را نشان می‌دهد:

return new Dictionary<IntamediaInvoiceStatus, string>
{
    { IntamediaInvoiceStatus.Draft, "ارسال نشده"},
    { IntamediaInvoiceStatus.Queued , "در صف ارسال"},
    { IntamediaInvoiceStatus.Sending , "در دست ارسال"},
    { IntamediaInvoiceStatus.Inquirying , "در دست استعلام"},
    { IntamediaInvoiceStatus.Errornous , "دارای خطا"},
    { IntamediaInvoiceStatus.Succeeded , "ارسال موفق"},
    { IntamediaInvoiceStatus.Edited , "ویرایش شده پس از ارسال"},
    { IntamediaInvoiceStatus.InProgress , "در حال پردازش"},
    { IntamediaInvoiceStatus.WaitingForInquiry , "در بازهٔ انتظار پیش از استعلام"},
    { IntamediaInvoiceStatus.NotFound , "NOT_FOUND"},
    { IntamediaInvoiceStatus.Unknown , "خطای ناشناخته"},
    { IntamediaInvoiceStatus.InquiryAborted , "خروج دستی از صف استعلام"},
    { IntamediaInvoiceStatus.SendFailed , "خطا در ارتباط با سامانه"},
    { IntamediaInvoiceStatus.SucceededInLastSession , "ارسال موفق (اخیر)"},
    { IntamediaInvoiceStatus.ChargeRunOut , "عدم ارسال به دلیل شارژ ناکافی"},
    { IntamediaInvoiceStatus.Timeout , "پردازش طولانی"},
    { IntamediaInvoiceStatus.NotCurrent , "غیرجاری"},
    { IntamediaInvoiceStatus.Reserved17 , "Reserved17"},
    { IntamediaInvoiceStatus.Reserved18 , "Reserved18"},
    { IntamediaInvoiceStatus.Reserved19 , "Reserved19"},
    { IntamediaInvoiceStatus.Current , "جاری"},
    { IntamediaInvoiceStatus.All , "همه"},
};

بعضی از وضعیتهای کد بالا مانند جاری یا همه وضعیت قابل انتساب به یک صورتحساب نیستند و برای فیلتر کردن وضعیت صورتحساب‌ها در گزارشات استفاده می‌شوند. تعریف جاری شامل تمام صورتحساب‌هایی می‌شود که در وضعیت ارسال موفق قرار ندارند. ارسال موفق اخیر همان ارسال موفق است ولی وضعیت جاری شامل آن می‌شود (کاربرد آن این است که کاربر بتواند پیش از خارج شدن صورتحساب‌های ارسال شده به شکل موفق از وضعیت جاری که فیلتر پیش‌فرض تدبیر مؤدیان است آنها را ببیند و بعد از ارسال یا ایجاد صورتحسابهای دیگر صورتحسابهای ارسال موفق اخیر به ارسال موفق تبدیل می‌شوند).

برای دریافت فهرست صورتحساب‌ها باید شرکت و وضعیت مدنظر را مشخص کنیم. کد زیر فهرست صورتحساب‌های شرکت با وضعیت مشخص شده را می‌دهد:

using (HttpClient httpClient = new HttpClient())
{
    await MoaddiyanSessionChecker.PrepareClientAsync(httpClient);
    var response = await httpClient.GetAsync
        (
        $"https://api.moaddiyan.com/api/invoice/{Settings.Default.WorkspaceId}?PageNumber=1&PageSize=50&status={(cmbStatus.SelectedItem as IntamediaInvoiceStatusDescriptor).IntamediaInvoiceStatus}&invoiceNumber=0"
        );
    if (response.StatusCode != HttpStatusCode.OK)
    {
        Cursor = Cursors.Default;
        Enabled = true;
        MessageBox.Show(JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync()));
        return;
    }
    response.EnsureSuccessStatusCode();
    Cursor = Cursors.Default;
    Enabled = true;
    var invoices = JsonConvert.DeserializeObject<IntamediaInvoice[]>(await response.Content.ReadAsStringAsync());
    if (invoices != null)
    {
        grd.DataSource = invoices;
    }
}

متد فهرست صورتحساب‌ها نتایج را به صورت صفحه‌بندی شده ارائه می‌دهد که می‌توانید اطلاعات مربوط به صفحه‌بندی را از سرآمد پاسخ که در تصویر زیر مشخص شده است دریافت کنید (ساختار این سرآمد در کلاس PaginationMetadata تعریف شده است):

اطلاعات صفحه‌بندی

در کد دریافت فهرست صورتحساب‌ها اگر بخواهیم اطلاعات صفحه‌بندی را هم نشان بدهیم چنین کدی را اضافه می‌کنیم:

var pagingMeta = JsonConvert.DeserializeObject<PaginationMetadata>(response.Headers.GetValues("paging-headers").Single());
lblPaging.Text = $"صفحهٔ {pagingMeta.currentPage} از {pagingMeta.totalPages} - تعداد کل: {pagingMeta.totalCount}";

کاربری که از توکن او برای دریافت اطلاعات استفاده می‌کنید باید دسترسی‌های کافی روی شرکت مدنظر داشته باشد و مثلاً اگر فاقد دسترسی مشاهده روی موجودیت صورتحساب باشد پاسخ دریافتی از وب‌سرویس برای او 403 خواهد بود.

برای ایجاد صورتحساب اطلاعات موجودیت صورتحساب را پرمی‌کنیم و آن را از طریق متد POST مربوطه ارسال می‌کنیم.

پیش‌نیاز ایجاد صورتحساب تعیین اطلاعات کلیدی آن است.

اولین مورد شماره یا سریال داخلی صورتحساب است. همچنان که از مستندات مشخص است این شماره باید روی حافظهٔ مالیاتی یکتا و صعودی باشد. برای تعیین مقدار بعدی آن باید اطلاعات آخرین صورتحساب ایجاد شده در شرکت را بگیرید و شمارهٔ آن را یکی اضافه کنید. تکه‌کد زیر همین کار را می‌کند:

    long invoiceNumber = 1;
    using (HttpClient httpClient = new HttpClient())
    {
        await MoaddiyanSessionChecker.PrepareClientAsync(httpClient);
        var response = await httpClient.GetAsync
            (
            $"https://api.moaddiyan.com/api/invoice/{Settings.Default.WorkspaceId}/last"
            );
        if (response.StatusCode != HttpStatusCode.OK)
        {
            MessageBox.Show(JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync()));
            return;
        }
        response.EnsureSuccessStatusCode();
        var lastInvoice = JsonConvert.DeserializeObject<IntamediaInvoice>(await response.Content.ReadAsStringAsync());
        if(lastInvoice != null)
        {
            invoiceNumber = lastInvoice.InvoiceNumber + 1;
        }
    }

تاریخ صدور صورتحساب، موضوع صورتحساب، نوع صورتحساب، الگوی صورتحساب و اطلاعات اقلام آن شامل شناسهٔ یکتای کالا یا خدمت، شرح، مقدار، مبلغ واحد، تخفیف، درصد مالیات بر ارزش افزوده حداقل اطلاعاتی هستند که برای ایجاد یک صورتحساب نوع دوم (بدون اطلاعات مشتری) با الگوی فروش لازم است. تکه‌کد زیر موارد یاد شده را مقداردهی کرده آن را به وب‌سرویس ارسال و مشخصات صورتحساب ایجاد شده را دریافت می‌کند و شمارهٔ منحصر به فرد مالیاتی آن را نمایش می‌دهد:


            var measurementUnit = IntamediaMeasurementUnit.Units.Where(u => u.Name == "عدد").Single(); //واحد اقلام صورتحساب

            IntamediaInvoice invoice = new IntamediaInvoice()
            {
                WorkspaceId = Settings.Default.WorkspaceId, //شرکت
                InvoiceNumber = invoiceNumber, //شماره یا سریال داخلی صورتحساب
                DateTime = DateTime.Now, //تاریخ و زمان صدور صورتحساب
                InvoiceSubject = IntamediaInvoiceSubject.Main,// موضوع صورتحساب
                InvoiceType = IntamediaInvoiceType.Type2, //نوع صورتحساب
                InvoicePattern = IntamediaInvoicePattern.Pattern1NormalSales,//الگوی صورتحساب
                RecalculateSums = true,//جمع‌ها به طور خودکار حساب شود
                AdditionalNote = "ایجاد شده با API",
                Items =
                [
                    new IntamediaInvoiceItem()
                    {
                        StuffId = "2900750000016",//شناسهٔ کالا / خدمت
                        Description = "نرم‌افزار تدبیر",//شرح
                        Amount = 2,//تعداد یا مقدار
                        MeasurementUnitCode = measurementUnit.Code,
                        MeasurementUnitName = measurementUnit.Name,
                        UnitPrice = 10_000_000,//مبلغ واحد به ریال
                        Discount = 0, //تخفیف سطر به ریال
                        ValueAddedTaxRateInPercent = 10,//درصد مالیات بر ارزش افزوده
                    },
                    new IntamediaInvoiceItem()
                    {
                        StuffId = "2330002811587",
                        Description = "خدمات پشتیبانی",
                        Amount = 3,
                        MeasurementUnitCode = measurementUnit.Code,
                        MeasurementUnitName = measurementUnit.Name,
                        UnitPrice = 2_000_000,
                        Discount = 500_000,
                        ValueAddedTaxRateInPercent = 10,
                    },
                ]
            };

            using (HttpClient httpClient = new HttpClient())
            {
                await MoaddiyanSessionChecker.PrepareClientAsync(httpClient);
                var response = await httpClient.PostAsync
                    (
                    $"https://api.moaddiyan.com/api/invoice/{Settings.Default.WorkspaceId}",
                     new StringContent(JsonConvert.SerializeObject(invoice), Encoding.UTF8, "application/json")
                    );
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    MessageBox.Show(JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync()));
                    return;
                }
                response.EnsureSuccessStatusCode();
                var newInvoice = JsonConvert.DeserializeObject<IntamediaInvoice>(await response.Content.ReadAsStringAsync());
                MessageBox.Show(newInvoice.TaxId);
            }