از مه ۲۰۲۵ تا فوریهٔ ۲۰۲۶ پلتفرمی برای کسبوکاری ساختم که کالاهایی میفروشد که به تحویل و نصب در محل نیاز دارند. مشتری سفارش میدهد و پرداخت میکند، انبار کالای دارای شمارهسریال را تخصیص میدهد، پیک تحویل میدهد، تکنسین نصب میکند و پنل مدیریت در هر مرحله پول و موجودی را دنبال میکند. بخش عمدهٔ کد را خودم نوشتم؛ یک توسعهدهندهٔ دیگر گزارشها و چند قابلیت کوچکتر را اضافه کرد.
زمینه
تحویل همراه با نصب، آدمهای بیشتری را از یک سفارش آنلاین معمولی درگیر میکند. مشتریها درخواست ثبت و پیگیری میکنند، کارشناسان مرکز تماس سفارش تلفنی میگیرند، کارکنان انبار کالا را وارد و خارج میکنند، پیکها کالا را میبرند و تکنسینها، اغلب در روزی دیگر، آن را نصب میکنند. واحد مالی هم سهم پیکها و تکنسینها را تسویه میکند و بازپرداختها را انجام میدهد. هر گروه نمای متفاوتی از همان سفارش لازم دارد.
مسئلهٔ مهندسی
- یک سفارش، پایانهای متعدد. درخواست از «در انتظار پرداخت» به «تخصیصدادهشده»، «در انتظار تأیید پیک»، «تحویل پیک»، «تحویلشده» و «نصبشده» میرود. ممکن است لغو هم بشود، در تحویل یا نصب ناموفق بماند، مشتری در یکی از دو مراجعه حاضر نباشد یا کالا مرجوع شود. هرکدام از این وضعیتها روی موجودی، پول و اعلانها اثر دارد.
- پول همراه سفارش حرکت میکند. پرداخت آنلاین، کد تخفیف و هزینهٔ اضافه، بازپرداخت، و سهم هر پیک و تکنسین همه به همان درخواست وصلاند.
- موجودی فیزیکی است. کالاها شمارهسریال دارند، با پرداخت درخواست تخصیص داده میشوند و شمارهسریالشان هنگام تحویل ثبت میشود. بازههای زمانی تحویل هم ظرفیت محدودی دارند.
- نیروهای میدانی با گوشی و در ترافیک کار میکنند. پیکها و تکنسینها به APIهای کوتاه و روشن و مسیرهای معقول نیاز دارند.
کاری که انجام دادم
- پنل مدیریت. یک اپلیکیشن DevExpress XAF Blazor روی XPO و SQL Server با رابط کاربری فارسی: نما برای همهٔ موجودیتهای کسبوکار، دسترسی نقشمحور، داشبورد برای هر نقش، نقشهٔ پیگیری تحویل، کنسول دیسپچ و گزارشهایی مثل عملیات روزانه، خلاصهٔ مالی روزانه، تسویههای سررسیدشده، پرداختهای ناموفق و تحلیل RFM مشتریان. Audit trail خود XAF تغییرات داده را ثبت میکند.
- Web APIهای نقشمحور. حدود ۹۰ endpoint در ASP.NET Core برای کلاینتهای موبایل و وب که بر اساس نقش دستهبندی شدهاند: مشتری (درخواست کالا و خدمات همراهش، بازههای زمانی تحویل، تغییر زمان، پرداخت، فاکتور، مرجوعی، امتیاز به تکنسین)، پیک (کارهای امروز، گذشته و آینده، تحویل همراه با ثبت شمارهسریال، حاضر نبودن مشتری، بهینهسازی مسیر، درآمد)، تکنسین (پذیرش یا رد تخصیص، تکمیل کار با عکس، محدودهٔ خدمت، درآمد)، انبار (رسید و موجودی) و مرکز تماس (مشتری، درخواست و تغییر وضعیت).
- ورود. کاربران با شمارهٔ موبایل و کد یکبارمصرف ثبتنام میکنند و وارد میشوند (پیکها با رمز عبور) و JWT میگیرند. هر endpoint نقشمحور فقط با رکورد پیک، تکنسین یا مشتریای کار میکند که نام کاربری داخل توکن به آن اشاره دارد.
- تخصیص خودکار تکنسین. یک hosted service هر شب اجرا میشود. درخواستهای تحویلشدهای را که هنوز نصاب ندارند، به ترتیب اولویت نصب و سپس قدمت، برمیدارد و به هر تکنسین فعال بر اساس فاصله (haversine)، قدمت و اولویت درخواست، امتیاز تکنسین و بار کاری فعلیاش امتیاز میدهد. سه نامزد برتر هر درخواست یک تخصیص میگیرند که میتوانند بپذیرند یا رد کنند و به مشتریها پیامک اطلاعرسانی فرستاده میشود.
- پرداخت. یک interface مشترک روی سه درگاه پرداخت ایرانی. سرویس بررسی میکند درگاه انتخابشده در دسترس هست یا نه و اگر نباشد سراغ درگاه دیگری میرود؛ فراخوانیهای HTTP به درگاهها با policyهای Polly دوباره تلاش میشوند.
- نقشه. تبدیل مختصات به نشانی (reverse geocoding)، فاصله با در نظر گرفتن ترافیک و بهینهسازی مسیر چندمقصدی از طریق یک API نقشهٔ ایرانی.
- اعلانها. کدهای یکبارمصرف و پیامکهای الگودار که با EF Core در یک پایگاهدادهٔ جداگانهٔ اعلان ثبت میشوند.
نکتههای طراحی
چرا XAF برای پنل مدیریت
پنل مدیریت دهها موجودیت کسبوکار دارد و هرکدام فهرست، فرم، فیلتر، دسترسی و اغلب گزارش لازم دارد. XAF اینها را از روی مدل داده میسازد، پس بیشتر وقت صرف گردشکارها و APIها شد، نه ساختن صفحهها. بهایش این است که قراردادهای فریمورک به کد شکل میدهند و تست واحدِ منطق کسبوکاری که داخل اشیای persistent قرار دارد سختتر است.
مهار موجودیت درخواست
درخواست مرکز مدل است و برای همین زود بزرگ شد. آن را بر اساس دغدغه به چند partial class تقسیم کردم (موجودیت، قیمتگذاری، مالی، وضعیت و ذخیرهسازی، زمانها) و تخصیص موجودی را به یک domain service جداگانه منتقل کردم. تغییر وضعیت در یک نقطه انجام میشود که زمان را ثبت میکند، وضعیت اقلام تخصیصیافتهٔ انبار را بهروز میکند و اعلان همان تغییر را میفرستد. بررسی ظرفیت بازهٔ تحویل هم وقتی اجرا میشود که درخواستِ پرداختشده برای اولین بار جلو میرود.
پردازش دستهای شبانه برای تخصیص
تخصیص بهصورت دستهای و شبانه اجرا میشود، نه همزمان با رسیدن هر درخواست. پردازش دستهای میتواند همهٔ درخواستهای باز را یکجا با همهٔ تکنسینها مقایسه کند و نتیجه پیش از شروع روز کاری آماده است. وزنها بدهبستانها را صریح میکنند: اولویت بیشترین وزن را دارد، بعد قدمت درخواست، و بعد امتیاز، بار کاری و فاصله. بهایش این است که این پردازش به تغییرات طول روز واکنش نشان نمیدهد.
درگاهها از کار میافتند
درگاههای پرداخت بیرون از کنترل سامانهاند و گاهی در دسترس نیستند. پنهان کردنشان پشت یک interface مشترک امکان جایگزینی را فراهم کرد: اگر درگاه انتخابشده از کار افتاده باشد، پرداخت روی درگاه دیگری شروع میشود و تأیید پرداخت به همان درگاهی میرود که برای آن تراکنش ثبت شده است.
تست، و قدم بعدی
تست خودکار نقطهٔ ضعف این کدبیس است. کل solution فقط یک تست end-to-end از نوع smoke دارد که با DevExpress EasyTest روی Selenium نوشته شده است: پنل مدیریت را روی یک پایگاهدادهٔ تازه بالا میآورد، وارد میشود و آیتمهای اصلی منو را باز میکند.
در ژانویهٔ ۲۰۲۶ یک بازطراحی ماژولار را شروع کردم. نقشهای از ماژولها نوشتم که پلتفرم را به چند bounded context تقسیم میکند (مشتری، پیک، تکنسین، انبار، تحویل حضوری، فروشگاه، مدیریت، مالی، هویت، پیامرسانی، قاعدههای قیمتگذاری، نقشه، پشتیبانی، ریسک و تحلیل داده) و برای هرکدام یک اسکلت clean architecture با پروژههای API، application، domain و infrastructure مبتنی بر EF Core ساختم. بعد ماژول مشتری به مخزن جداگانهٔ خودش رفت و یک توسعهدهندهٔ دیگر سبد خرید، سفارشها و درخواستهای خدمت را در آن توسعه داد. این شروع یک مهاجرت است، نه یک مهاجرت تمامشده.
فناوریها
- .NET 8، ASP.NET Core Web API، JWT
- DevExpress XAF 25.1: رابط Blazor، XPO، Web API، گزارشها، داشبوردها، audit trail
- SQL Server؛ EF Core برای انبارهٔ اعلانها
- Polly، Swagger
- xUnit، DevExpress EasyTest، Selenium
وضعیت
- پلتفرمی که سفارش، پرداخت، موجودی، تحویل، نصب، تسویه و گزارشگیری را پوشش میدهد، با یک پنل مدیریت و APIهای نقشمحور برای پنج گروه کاربر.
- آخرین commit مخزن اصلی مربوط به فوریهٔ ۲۰۲۶ است. بازطراحی ماژولار به شکل مستندات و اسکلت پروژهها وجود دارد و ماژول مشتری از بقیه جلوتر است.
مطالب مرتبط
دستور کارهای خدمات در محل (work order) در پلتفرم میکروسرویسی چندمستأجری هم آمدهاند که از اول به شکل سرویسهای جدا ساخته میشود. اگر به پنل مدیریت یا سامانهٔ خدمات در محل روی .NET نیاز دارید، صفحهٔ همکاری را ببینید.
پرسشهای رایج
این پلتفرم تحویل و خدمات در محل چه کاری انجام میدهد؟
سفارش کالاهایی را اداره میکند که به تحویل و نصب در محل نیاز دارند. مشتری سفارش میدهد و پرداخت میکند، انبار کالای دارای شمارهسریال را تخصیص میدهد، پیک تحویل میدهد، تکنسین نصب میکند و پنل مدیریت پرداختها، بازپرداختها، تسویهها و گزارشها را دنبال میکند.
کارها چطور به تکنسینها تخصیص داده میشوند؟
یک سرویس پسزمینه هر شب اجرا میشود. درخواستهای تحویلشدهای را که هنوز نصاب ندارند بر اساس اولویت و قدمت مرتب میکند، به هر تکنسین فعال بر اساس فاصله، قدمت و اولویت درخواست، امتیاز تکنسین و بار کاری فعلیاش امتیاز میدهد و کار را به سه نامزد برتر پیشنهاد میکند که میتوانند آن را بپذیرند یا رد کنند.
این پلتفرم با چه فناوریهایی ساخته شده است؟
روی .NET 8 ساخته شده، با پنل مدیریت DevExpress XAF Blazor روی XPO و SQL Server، Web APIهای ASP.NET Core که با JWT محافظت میشوند، EF Core برای انبارهٔ اعلانها و Polly برای تلاش دوباره در فراخوانی درگاههای پرداخت.
نقش مهدی مولوی در این پروژه چه بود؟
پلتفرم را ساخت و بخش عمدهٔ کد آن را بین مه ۲۰۲۵ و فوریهٔ ۲۰۲۶ نوشت. یک توسعهدهندهٔ دیگر گزارشها و چند قابلیت کوچکتر را اضافه کرد.
در همین بخش
- پلتفرم آزمون تطبیقی: سنجش مبتنی بر IRT برای حدود ۸۹ هزار دانشآموز
- پردازش سؤال با هوش مصنوعی: از PDF آزمون تا سؤالهای ساختیافته در LaTeX
- «همسایه»: سامانهٔ ابری مدیریت ساختمان و مجتمع مسکونی
- دسترسپذیری بالا در اوج آزمونها: Redis Sentinel برای ۴۵ تا ۷۰ هزار کاربر همزمان
- سامانهٔ حسابداری حوالههای ارزی برای صرافی بانک رفاه
- داشبورد شاخصهای مالی برای MBRI
- پلتفرم میکروسرویسی چندمستأجری: زیرساخت مستأجر، پیامرسانی و گردشکار (در حال توسعه)
مهدی مولوی