۴. ثبت فروش و گزارش

خط ۵۰ تا ۶۳

دو متد آخر کلاس به فروش مربوط‌اند. sell موجودی کالا را کم می‌کند و یک رکورد فروش می‌سازد، و report از روی همه‌ی رکوردها تعداد کل و درآمد کل را حساب می‌کند.

کد این بخش

shop_manager.py, lines 50–63
50    def sell(self, name, qty):51        if name not in self.products:52            raise ValueError('کالا پیدا نشد')53        if qty <= 0:54            raise ValueError('تعداد باید مثبت باشد')55        product = self.products[name]56        if qty > product['qty']:57            raise ValueError('موجودی کافی نیست')58        product['qty'] -= qty59        self.sales.append({'name': name, 'qty': qty, 'total': qty * product['sell']})60        self.save()61 62    def report(self):63        return sum(s['qty'] for s in self.sales), sum(s['total'] for s in self.sales)

روی شماره‌ی هر خط بزن تا به توضیحش بروی؛ روی «خط …» در توضیح بزن تا همان خط در کد روشن شود.

توضیح خط به خط

خط ۵۰def sell(self, name, qty):
متد sell نام کالا و تعداد فروش را می‌گیرد. از تابع sell بیرون کلاس (خط ۱۰۳) صدا زده می‌شود.
خط ۵۱if name not in self.products:
کالا باید وجود داشته باشد.
خط ۵۲raise ValueError('کالا پیدا نشد')
خطای «کالا پیدا نشد».
خط ۵۳if qty <= 0:
تعداد فروش باید بزرگ‌تر از صفر باشد. اینجا برخلاف خط ۳۱ صفر هم پذیرفته نمی‌شود، چون فروش صفر عدد معنی ندارد.
خط ۵۴raise ValueError('تعداد باید مثبت باشد')
خطای «تعداد باید مثبت باشد».
خط ۵۵product = self.products[name]
دیکشنری کالا را در متغیر product می‌گذارد. این کپی نیست، یک «ارجاع» به همان دیکشنری داخل self.products است؛ هر تغییری روی product مستقیم روی داده‌ی اصلی اثر می‌گذارد.
خط ۵۶if qty > product['qty']:
اگر تعداد درخواستی از موجودی بیشتر باشد...
خط ۵۷raise ValueError('موجودی کافی نیست')
...خطای «موجودی کافی نیست».
خط ۵۸product['qty'] -= qty
-= یعنی «از خودش کم کن». موجودی کالا به اندازه‌ی تعداد فروش کم می‌شود. چون product ارجاع است (خط ۵۵)، self.products هم عوض شده است.
خط ۵۹self.sales.append({'name': name, 'qty': qty, 'total': qty * product['sell']})
یک رکورد فروش به انتهای لیست اضافه می‌کند. total با قیمت فروش همین لحظه حساب می‌شود، پس اگر بعداً قیمت را ویرایش کنی (خط ۴۱)، مبلغ فروش‌های قبلی ثابت می‌ماند.
خط ۶۰self.save()
ذخیره‌ی هر دو تغییر (موجودی و فروش) در فایل.
خط ۶۱
خط خالی.
خط ۶۲def report(self):
متد report ورودی ندارد و فقط حساب می‌کند. از تابع report بیرون کلاس (خط ۱۱۰) صدا زده می‌شود.
خط ۶۳return sum(s['qty'] for s in self.sales), sum(s['total'] for s in self.sales)
دو «عبارت مولد» (generator expression) را جمع می‌زند: اولی qty همه‌ی فروش‌ها، دومی total همه‌ی فروش‌ها. چون دو مقدار با کاما جدا شده‌اند، خروجی یک تاپل است، مثلاً (2, 80000000000.0).

یادداشت

مثال: اگر در sales دو رکورد {'qty': 2, 'total': 30} و {'qty': 5, 'total': 100} باشد، report() مقدار (7, 130) را برمی‌گرداند.

ارتباط با بقیه‌ی کد

همچنین ببینید

Shop.sell
با دکمه‌ی «ثبت فروش»: تابع sell خط ۱۰۲ ← shop.sell(نام، تعداد) خط ۱۰۳.
Shop.report
با دکمه‌ی «گزارش فروش»: تابع report خط ۱۰۹ ← خط ۱۱۰، که تاپل را به count و income باز می‌کند و در خط ۱۱۱ نشان می‌دهد.
self.sales
در سازنده خط ۱۱ ساخته و در load خط ۲۴ از فایل پر می‌شود.
product['qty']
موجودی کم‌شده بعد از فروش، با refresh در خط ۸۷ دوباره در جدول نمایش داده می‌شود (خط ۱۱۸).

سؤال‌های تمرینی

اول خودت جواب بده، بعد جواب را باز کن.

  1. چرا product['qty'] -= qty در خط ۵۸ خود self.products را هم عوض می‌کند؟

    نمایش جواب

    چون product در خط ۵۵ ارجاع به همان دیکشنری است، نه کپی آن.

  2. فروش ۰ عدد خطا می‌دهد ولی قیمت ۰ پذیرفته می‌شود. کدام خط‌ها این را تعیین می‌کنند؟

    نمایش جواب

    خط ۵۳ برای فروش qty <= 0 را رد می‌کند؛ خط ۳۱ برای قیمت فقط عددهای کمتر از ۰ را رد می‌کند.

  3. وقتی هنوز فروشی ثبت نشده، report() چه برمی‌گرداند؟

    نمایش جواب

    (0, 0)، چون sum یک دنباله‌ی خالی صفر است.

  4. اگر بعد از فروش قیمت کالا ویرایش شود، total آن فروش عوض می‌شود؟

    نمایش جواب

    نه. خط ۵۹ مبلغ را در لحظه‌ی فروش ذخیره می‌کند.