def products(request, company): c = get_object_or_404(Company, url_name=company) # needs to be at least guest to view products if not has_permission(request.user, c, 'product', 'view'): return no_permission_view(request, c, _("You have no permission to view products.")) # if there are no taxes defined, don't show anything if Tax.objects.filter(company=c).count() == 0: return error(request, c, _("There are no taxes defined. Please go to tax management and define them.")) # if there are no categories defined, throw an error if Category.objects.filter(company=c).count() == 0: return error(request, c, _("There are no categories defined, please go to category management to define them.")) # fields that need to be limited in length: lengths = { 'code': max_field_length(Product, 'code'), 'price': g.DECIMAL['currency_digits'] + 1, 'purchase_price': g.DECIMAL['currency_digits'] + 1, 'shortcut': max_field_length(Product, 'shortcut'), 'stock': g.DECIMAL['quantity_digits'], 'name': max_field_length(Product, 'name'), 'tax': g.DECIMAL['percentage_decimal_places'] + 4, # up to '100.' + 'decimal_digits' } context = { 'company': c, 'title': _("Products"), 'site_title': g.MISC['site_title'], # lists 'taxes': JsonStringify(get_all_taxes(request.user, c)), 'categories': JsonStringify(get_all_categories(c, json=True)), 'units': JsonStringify(g.UNITS), 'discounts': JsonStringify(get_all_discounts(request.user, c)), # urls for ajax calls 'add_url': reverse('pos:create_product', args=[c.url_name]), # config variables 'can_edit': has_permission(request.user, c, 'product', 'edit'), 'currency': get_company_value(request.user, c, 'pos_currency'), # images 'image_dimensions': g.IMAGE_DIMENSIONS['product'], 'image_upload_formats': g.MISC['image_upload_formats'], # what can be uploaded 'max_upload_size': round(g.MISC['max_upload_image_size']/2**20, 2), # show in megabytes 'max_upload_size_bytes': g.MISC['max_upload_image_size'], # bytes for javascript # html fields 'field_lengths': lengths, 'separator': get_company_value(request.user, c, 'pos_decimal_separator'), # numbers etc 'default_tax_id': get_default_tax(request.user, c)['id'], 'decimal_places': get_company_value(request.user, c, 'pos_decimal_places')*2, # ACHTUNG: rounding comes at the end } return render(request, 'pos/manage/products.html', context)
def set_serial(instance, **kwargs): if instance.serial: return # set serial number after the bill has been paid; from config.functions import get_company_value # check the prefix of bills bill_format = get_company_value(instance.created_by, instance.company, 'pos_bill_serial_format') if bill_format == 's': prefix = '' elif bill_format == 'yyyy-s': prefix = str(instance.timestamp.year) + '-' elif bill_format == 'yyyy-m-s': prefix = str(instance.timestamp.year) + '-' + str(instance.timestamp.month) + '-' else: raise ValueError("Unknown bill prefix type") # find bills that match the prefix last_bill = Bill.objects.filter(company=instance.company, serial_prefix=prefix) if last_bill.exists(): # use the serial from the last matching bill and add 1 serial_number = last_bill.order_by('-serial_number')[0].serial_number + 1 else: serial_number = 1 instance.serial_number = serial_number instance.serial_prefix = prefix instance.serial = prefix + str(serial_number)
def parse_decimal(user, company, string, max_digits=None): """ replace user's decimal separator with dot and parse return dictionary with result status and parsed number: {'success':True/False, number:<num>/None} """ from config.functions import get_company_value if user: # if user is None, try with the dot (user should never be None - # this is to avoid checking on every function call) string = string.replace(get_company_value(user, company, 'pos_decimal_separator'), '.') # check for entries too big if max_digits: if string.find('.') == -1: # it's an integer, it has no 'decimal point' if len(string) > max_digits: return {'success': False, 'number': None} if string.find('.') > max_digits: # it's a float, the integer part shouldn't be longer than max_digits return {'success': False, 'number': None} try: number = Decimal(string) return {'success': True, 'number': number} except: return {'success': False, 'number': None}
def company_config_to_dict(user, company): return { 'company_id': company.id, 'pos_decimal_separator': get_company_value(user, company, 'pos_decimal_separator'), 'pos_decimal_places': get_company_value(user, company, 'pos_decimal_places'), 'pos_time_format': get_company_value(user, company, 'pos_time_format'), 'pos_date_format': get_company_value(user, company, 'pos_date_format'), 'pos_timezone': get_company_value(user, company, 'pos_timezone'), 'pos_currency': get_company_value(user, company, 'pos_currency'), 'pos_bill_serial_format': get_company_value(user, company, 'pos_bill_serial_format'), 'pos_payment_bitcoin_address': get_company_value(user, company, 'pos_payment_bitcoin_address'), 'pos_payment_paypal_address': get_company_value(user, company, 'pos_payment_paypal_address') }
def format_number(user, company, n, high_precision=False): """ returns formatted decimal number n; strips zeros, but leaves <p> numbers after decimal point even if they are zero """ from config.functions import get_company_value sep = get_company_value(user, company, 'pos_decimal_separator') p = int(get_company_value(user, company, 'pos_decimal_places')) if not n: return '0' if high_precision: s = str(n.quantize(Decimal('1.'+'0'*2*p))) else: s = str(n.quantize(Decimal('1.' + '0' * p))) return s.replace('.', sep)
def company_settings(request, company): c = get_object_or_404(Company, url_name=company) # permissions if not has_permission(request.user, c, 'config', 'edit'): return no_permission_view(request, c, _("You have no permission to edit system configuration.")) # get config initial = { 'date_format': get_company_value(request.user, c, 'pos_date_format'), 'time_format': get_company_value(request.user, c, 'pos_time_format'), 'timezone': get_company_value(request.user, c, 'pos_timezone'), 'currency': get_company_value(request.user, c, 'pos_currency'), 'decimal_separator': get_company_value(request.user, c, 'pos_decimal_separator'), 'decimal_places': get_company_value(request.user, c, 'pos_decimal_places'), 'payment_bitcoin_address': get_company_value(request.user, c, 'pos_payment_bitcoin_address'), 'payment_paypal_address': get_company_value(request.user, c, 'pos_payment_paypal_address'), 'bill_serial_format': get_company_value(request.user, c, 'pos_bill_serial_format') } if request.method == 'POST': form = ConfigForm(request.POST) new_config = {} if form.is_valid(): for key in initial: new_config['pos_' + key] = form.cleaned_data[key] save_company_config(request.user, c, new_config) else: form = ConfigForm(initial=initial) # An unbound form context = { 'company': c, 'form': form, 'title': _("System configuration"), 'site_title': g.MISC['site_title'], } return render(request, 'pos/manage/config.html', context)
def list_registers(request, company): c = get_object_or_404(Company, url_name=company) # check permissions: needs to be guest if not has_permission(request.user, c, 'register', 'view'): return no_permission_view(request, c, _("You have no permission to view registers.")) context = { 'company': c, 'title': _("Cash Registers"), 'registers': Register.objects.filter(company__id=c.id).order_by('name'), 'site_title': g.MISC['site_title'], 'date_format_django': get_date_format(request.user, c, 'django'), 'date_format_js': get_date_format(request.user, c, 'js'), 'currency': get_company_value(request.user, c, 'pos_currency'), } return render(request, 'pos/manage/registers.html', context)
def get_btc_amount(self, user, company): """ This method should be used when paying, otherwise get amount from self.total_btc """ datetime_updated_with_offset = self.datetime_updated + datetime.timedelta(hours=int(settings.PAYMENT_OFFICER["bitcoin_payment_waiting_interval"])) if self.status == WAITING: if self.total_btc is None or datetime_updated_with_offset < datetime.datetime.now(): currency = get_company_value(user, company, 'pos_currency') btc_price = calculate_btc_price(currency, self.total) if btc_price != -1: self.total_btc = btc_price self.save() return self.total_btc
def list_taxes(request, company): c = get_object_or_404(Company, url_name=company) # permissions list_permission = has_permission(request.user, c, 'tax', 'view') edit_permission = has_permission(request.user, c, 'tax', 'edit') if not list_permission: return no_permission_view(request, c, _("You have no permission to view taxes.")) context = { 'company': c, 'taxes': JsonStringify(get_all_taxes(request.user, c)), 'edit_permission': edit_permission, 'max_name_length': max_field_length(Tax, 'name'), 'title': _("Manage Tax Rates"), 'site_title': g.MISC['site_title'], 'separator': get_company_value(request.user, c, 'pos_decimal_separator'), } return render(request, 'pos/manage/tax.html', context)
def stats(request, company): c = Company.objects.get(url_name=company) # permission? if not has_permission(request.user, c, 'stats', 'view'): return no_permission_view(request, c, _("You have no permission to view stats")) # recent earnings: all bills from today/yesterday/this week/this month, # their income and profit def bill_earnings(start_date=None, end_date=None): billset = Bill.objects.filter(company=c, payment__status=PAID) if start_date: billset = billset.filter(timestamp__gte=start_date) if end_date: billset = billset.filter(timestamp__lt=end_date) if billset.count() == 0: return { 'income': format_number(request.user, c, Decimal(0)), 'profit': format_number(request.user, c, Decimal(0)), } income = billset.aggregate(Sum('payment__total'))['payment__total__sum'] return { 'income': format_number(request.user, c, income), 'profit': format_number(request.user, c, income - billset.aggregate(Sum('base'))['base__sum'] - billset.aggregate(Sum('discount'))['discount__sum']) } today = dtm.datetime.utcnow().replace(tzinfo=timezone(get_company_value(request.user, c, 'pos_timezone'))) today = today.replace(hour=0, minute=0, second=0, microsecond=0) yesterday = today - dtm.timedelta(days=1) week_start = today - dtm.timedelta(days=today.weekday()) month_start = today.replace(day=1) # get custom dates from form, if they are present earnings_from = None earnings_to = None if request.method == "GET": r = parse_date(request.user, c, request.GET.get('earnings_from')) if not r['success']: earnings_from = None else: earnings_from = r['date'] r = parse_date(request.user, c, request.GET.get('earnings_to')) if not r['success']: earnings_to = None else: earnings_to = r['date'] if earnings_from and earnings_to and earnings_from > earnings_to: earnings_from = None earnings_to = None if earnings_from and earnings_to: custom_earnings = bill_earnings(start_date=earnings_from, end_date=earnings_to) else: custom_earnings = None earnings = { 'today': bill_earnings(start_date=today), 'yesterday': bill_earnings(start_date=yesterday, end_date=today), 'week': bill_earnings(start_date=week_start), 'month': bill_earnings(start_date=month_start), 'custom': custom_earnings, } # top-selling products: # today/yesterday/this week/this/month/overall TOP_PRODUCTS_LEN = 10 def top_products(start_date=None, end_date=None): itemset = BillItem.objects.filter(bill__company=c, bill__payment__status=PAID) if start_date: itemset = itemset.filter(bill__timestamp__gte=start_date) if end_date: itemset = itemset.filter(bill__timestamp__lt=end_date) itemset = itemset.values('name', 'code')\ .annotate(sold_quantity=Sum('quantity'))\ .order_by('-sold_quantity')[:TOP_PRODUCTS_LEN] # make sure the list is always exactly TOP_PRODUCTS_LEN long itemset = list(itemset) itemset += [None for t in range(TOP_PRODUCTS_LEN - len(itemset))] return itemset # custom date limits from form products_from = None products_to = None if request.method == "GET": r = parse_date(request.user, c, request.GET.get('products_from')) if not r['success']: products_from = None else: products_from = r['date'] r = parse_date(request.user, c, request.GET.get('products_to')) if not r['success']: products_to = None else: products_to = r['date'] if products_from and products_to and products_from > products_to: products_from = None products_to = None if products_from and products_to: custom_products = top_products(start_date=products_from, end_date=products_to) else: custom_products = [None for x in range(TOP_PRODUCTS_LEN)] products_data = { 'all_time': top_products(), 'yesterday': top_products(start_date=yesterday, end_date=today), 'week': top_products(start_date=week_start), 'month': top_products(start_date=month_start), 'custom': custom_products, } # convert the products from dict of lists to a simple list of rows for the template products = [] for i in range(TOP_PRODUCTS_LEN): if not products_data['all_time'][i]: break products.append({ 'all_time': products_data['all_time'][i], 'yesterday': products_data['yesterday'][i], 'week': products_data['week'][i], 'month': products_data['month'][i], 'custom': products_data['custom'][i], }) context = { 'company': c, 'earnings': earnings, 'earnings_from': format_date(request.user, c, earnings_from), 'earnings_to': format_date(request.user, c, earnings_to), 'products': products, 'products_from': format_date(request.user, c, products_from), 'products_to': format_date(request.user, c, products_to), 'title': _("Stats"), 'site_title': g.SITE_TITLE, 'date_format_js': get_date_format(request.user, c, 'js') } return render(request, 'pos/manage/stats.html', context)
def manage_stock(request, company, page): c = get_object_or_404(Company, url_name=company) # check permissions: needs to be guest if not has_permission(request.user, c, 'stock', 'view'): return no_permission_view(request, c, _("You have no permission to view stock.")) documents = Document.objects.filter(company=c).order_by('-number') search = request.GET.get('search', '') document = request.GET.get('document', '') if search == "": if document == "" or document == "all": stocks = Stock.objects.filter(company=c).order_by('-datetime_created') else: stocks = Stock.objects.filter(company=c, document=document).order_by('-datetime_created') else: stock_products = StockProduct.objects.filter(product__name__icontains=search, stock__in=[s.id for s in Stock.objects.filter(company=c)]) stocks = Stock.objects.filter(Q(name__icontains=search) | Q(id__in=[sp.id for sp in stock_products]), Q(company=c)).order_by('-datetime_created') paginator = Paginator(stocks, g.MISC['stocks_per_page']) if page: stocks = paginator.page(page) else: stocks = paginator.page(1) stocks_json = {} for s in stocks: s.stock_products = StockProduct.objects.filter(stock=s).order_by('product__name') __stock_products = {} for sp in s.stock_products: __stock_products[str(sp.id)] = {'product_id': str(sp.product.id), 'product_name': str(sp.product.name), 'deduction': str(fn(sp.deduction))} stocks_json[s.id] = { 'id': str(s.id), 'stock_type': s.stock_type, 'unit_type': s.unit_type, 'stock': fn(s.stock), 'left_stock': fn(s.left_stock), 'stock_products': __stock_products } currency = get_company_value(request.user, c, 'pos_currency') context = { 'separator': get_company_value(request.user, c, 'pos_decimal_separator'), 'currency': currency, 'decimal_places': get_company_value(request.user, c, 'pos_decimal_places'), 'currency_symbol': currencies[currency][1], 'documents': documents, 'managing': 'stock', 'units': g.UNITS, 'stock_types': g.STOCK_TYPE, 'company': c, 'stocks': stocks, 'stocks_json': json.dumps(stocks_json), # 'searched': searched, # 'filter_form': form, 'title': _("Stock"), 'next_url': reverse('pos:manage_stock', args=[c.url_name, int(page)+1]), 'prev_url': reverse('pos:manage_stock', args=[c.url_name, int(page)-1]), 'site_title': g.MISC['site_title'], 'date_format_django': get_date_format(request.user, c, 'django'), 'date_format_js': get_date_format(request.user, c, 'js'), } return render(request, 'pos/manage/manage_stocks.html', context)
def list_bills(request, company): c = get_object_or_404(Company, url_name=company) # check permissions: needs to be guest if not has_permission(request.user, c, 'bill', 'view'): return no_permission_view(request, c, _("You have no permission to view bills.")) N = 10 # if no search was made, display last N bills (below) searched = False # use GET for everything: there's little parameters and when using POST, paginator gets # in the way with GET requests form = BillSearchForm(data=request.GET, user=request.user, company=c) if form.is_valid(): # decide which way to order the results # this is the fake switch statement that is missing in python for no obvious reason ordering = { 'serial': 'serial', 'date': 'timestamp', 'amount': 'total' } order = ordering.get(form.cleaned_data.get('sort_by')) if form.cleaned_data.get('sort_order') == 'desc': order = '-' + order bills = Bill.objects.filter(company=c).order_by(order) # filter by whatever is in the form: # issue date: from t = form.cleaned_data.get('issued_from') if t: bills = bills.filter(timestamp__gte=t) # issue date: to t = form.cleaned_data.get('issued_to') if t: bills = bills.filter(timestamp__lte=t) # item: t = form.cleaned_data.get('item_code') if t: ids = [i.id for i in BillItem.objects.only('bill_id').filter(code__icontains=t)] # find all bills that include item that contains this code bills = bills.filter(id__in=ids) # contact t = form.cleaned_data.get('contact') if t: bills = bills.filter(contact__first_name__icontains=t) | \ bills.filter(contact__last_name__icontains=t) | \ bills.filter(contact__company_name__icontains=t) # bill number t = form.cleaned_data.get('serial') if t: bills = bills.filter(serial__icontains=t) # status t = form.cleaned_data.get('status') if t: bills = bills.filter(payment__status=t) # amount: from t = form.cleaned_data.get('amount_from') if t: bills = bills.filter(total__gte=t) # amount: to t = form.cleaned_data.get('amount_to') if t: bills = bills.filter(total__lte=t) # user t = form.cleaned_data.get('user_name') if t: bills = bills.filter(user_name__icontains=t) page = form.cleaned_data.get('page') searched = True else: form = BillSearchForm(data=None, user=request.user, company=c) page = 1 bills = Bill.objects.filter(company=c).order_by('-timestamp') # format all bills manually bills = [bill_to_dict(request.user, c, b) for b in bills] paginator = Paginator(bills, g.MISC['bills_per_page']) if page: bills = paginator.page(page) else: bills = paginator.page(1) context = { 'company': c, 'bills': bills, 'searched': searched, 'filter_form': form, 'title': _("Bills"), 'site_title': g.MISC['site_title'], 'date_format_django': get_date_format(request.user, c, 'django'), 'date_format_js': get_date_format(request.user, c, 'js'), 'currency': get_company_value(request.user, c, 'pos_currency'), } return render(request, 'pos/manage/bills.html', context)
def list_discounts(request, company): c = get_object_or_404(Company, url_name=company) # check permissions: needs to be guest if not has_permission(request.user, c, 'discount', 'view'): return no_permission_view(request, c, _("You have no permission to view discounts.")) N = 6 searched = False # show the filter form form = DiscountFilterForm(data=request.GET, user=request.user, company=c) if form.is_valid(): discounts = Discount.objects.filter(company__id=c.id) # filter by whatever is in the form: description if form.cleaned_data.get('search'): discounts = discounts.filter(description__icontains=form.cleaned_data['search']) | \ discounts.filter(code__icontains=form.cleaned_data['search']) # start_date t = form.cleaned_data.get('start_date') if t: discounts = discounts.filter(start_date__gte=t) # end_date t = form.cleaned_data.get('end_date') if t: discounts = discounts.filter(start_date__gte=t) # enabled t = form.cleaned_data.get('enabled') if t is not None: discounts = discounts.filter(enabled=t) page = form.cleaned_data.get('page') searched = True # search results are being displayed else: form = DiscountFilterForm(user=request.user, company=c) discounts = Discount.objects.filter(company=c)[:N] page = 1 paginator = Paginator(discounts, g.MISC['discounts_per_page']) if page: discounts = paginator.page(page) else: discounts = paginator.page(1) context = { 'company': c, 'discounts': discounts, 'searched': searched, 'filter_form': form, 'title': _("Discounts"), 'site_title': g.MISC['site_title'], 'date_format_django': get_date_format(request.user, c, 'django'), 'date_format_js': get_date_format(request.user, c, 'js'), 'currency': get_company_value(request.user, c, 'pos_currency'), } return render(request, 'pos/manage/discounts.html', context)
def create_bill_(request, c): def item_error(message, product): return JsonError(message + " " + _("(Item" + ": ") + product.name + ")") # check permissions if not has_permission(request.user, c, "bill", "edit"): return JsonError(_("You have no permission to create bills")) # get data data = JsonParse(request.POST.get("data")) if not data: return JsonError(_("No data received")) # see if we're updating an existing bill existing_bill = None try: existing_bill = Bill.objects.get(company=c, id=int(data.get("id"))) if existing_bill.status == g.PAID: return JsonError(_("This bill has already been paid, editing is not possible")) except (ValueError, TypeError): pass except Bill.DoesNotExist: pass # current company (that will be fixed on this bill forever): # save a FK to BillCompany; the last company is the current one bill_company = BillCompany.objects.filter(company=c).order_by("-datetime_created")[0] # current register: get BillRegister with the same id as current one try: bill_registers = BillRegister.objects.filter(register__id=int(data.get("register_id"))) if len(bill_registers) > 0: bill_register = bill_registers[0] else: raise Register.DoesNotExist except (TypeError, ValueError, Register.DoesNotExist): return JsonError(_("Invalid register specified.")) # current contact: get BillContact with the same id as the requested contact if data.get("contact"): try: bill_contacts = BillContact.objects.get(contact__id=int(data.get("contact").get("id"))).order_by( "datetime_created" ) if len(bill_contacts) > 0: bill_contact = bill_contacts[0] else: raise Contact.DoesNotExist except (Contact.DoesNotExist, ValueError, TypeError): return JsonError(_("Invalid contact")) else: bill_contact = None # save all validated stuff in bill to a dictionary and insert into database at the end # prepare data for insert bill = { "company": c, "issuer": bill_company, "register": bill_register, "contact": bill_contact, "user_id": request.user.id, "user_name": str(request.user), "notes": data.get("notes", "")[: max_field_length(Bill, "notes")], "type": g.CASH, "status": g.WAITING, "items": [], "currency": get_company_value(request.user, c, "pos_currency"), # numbers... "base": Decimal(0), "discount": Decimal(0), "tax": Decimal(0), "total": Decimal(0), "created_by": request.user, } # timestamp try: # timestamp: send in an array of number: # [year, month, day, hour, minute, second] tn = [int(n) for n in data.get("timestamp")] bill["timestamp"] = dtm(year=tn[0], month=tn[1], day=tn[2], hour=tn[3], minute=tn[4], second=tn[5]) except (ValueError, TypeError): return JsonError(_("Invalid timestamp")) r = parse_decimal(request.user, c, data.get("total")) if not r["success"] or r["number"] <= Decimal("0"): return JsonError(_("Invalid grand total value")) else: bill["total"] = r["number"] # validate items for i in data.get("items"): # get product try: product = Product.objects.get(company=c, id=int(i.get("product_id"))) except Product.DoesNotExist: return JsonError(_("Product with this id does not exist") + " (id=" + i.get("product_id") + ")") # parse quantity r = parse_decimal(request.user, c, i.get("quantity"), g.DECIMAL["quantity_digits"]) if not r["success"]: return item_error(_("Invalid quantity value"), product) else: if r["number"] <= Decimal("0"): return item_error(_("Cannot add an item with zero or negative quantity"), product) quantity = r["number"] # remove from stock; TODO: check negative quantities (?) # actually we leave negative quantities as they are or # when stock is empty, we leave it at 0 product.destockify(quantity) product.save() item = { "created_by": request.user, "code": product.code, "shortcut": product.shortcut, "name": product.name, "description": product.description, "private_notes": product.private_notes, "unit_type": product.get_unit_type_display(), # ! display, not the 'code' "stock": product.stock, # 'bill': not now, after bill is saved "product_id": product.id, "bill_notes": i.get("bill_notes"), "discounts": [], # validated discounts (FK in database) # prices: will be calculated when discounts are ready "base": None, "quantity": None, "tax_rate": None, "batch": None, "discount": None, "net": None, "tax": None, "total": None, } bill["items"].append(item) for d in i["discounts"]: # check: # discount id: if it's -1, it's a unique discount on this item; # if it's anything else, the discount must belong to this company # and must be active and enabled d_id = int(d.get("id")) if d_id != -1: try: dbd = Discount.objects.get(id=d_id, company=c) if not dbd.is_active: return item_error(_("The discount is not active"), product) except Discount.DoesNotExist: return item_error(_("Chosen discount does not exist or is not valid"), product) # amount: parse number and check that percentage does not exceed 100% r = parse_decimal(request.user, c, d.get("amount")) if not r["success"]: return item_error(_("Invalid discount amount"), product) else: d_amount = r["number"] if d_amount < Decimal(0) or (d.get("type") == "Relative" and d_amount > Decimal(100)): return item_error(_("Invalid discount amount"), product) # save data to bill discount = { "id": d_id, "code": d.get("code"), "description": d.get("description"), "type": d.get("type"), "amount": d_amount, } item["discounts"].append(discount) # save this item's prices to item's dictionary (will go into database later) try: item["base"] = parse_decimal_exc(request.user, c, i.get("base"), message=_("Invalid base price")) item["quantity"] = parse_decimal_exc(request.user, c, i.get("quantity"), message=_("Invalid quantity")) item["tax_rate"] = parse_decimal_exc(request.user, c, i.get("tax_rate"), message=_("Invalid tax rate")) item["batch"] = parse_decimal_exc(request.user, c, i.get("batch"), message=_("Invalid batch price")) item["discount"] = parse_decimal_exc( request.user, c, i.get("discount"), message=_("Invalid discount amount") ) item["net"] = parse_decimal_exc(request.user, c, i.get("net"), message=_("Invalid net price")) item["tax"] = parse_decimal_exc(request.user, c, i.get("tax"), message=_("Invalid tax amount")) item["total"] = parse_decimal_exc(request.user, c, i.get("total"), message=_("Invalid total")) bill["base"] += item["batch"] bill["discount"] += item["discount"] bill["tax"] += item["tax"] except ValueError as e: return item_error(e.message, product) # at this point, everything is fine, insert into database if existing_bill: existing_bill.delete() bill_payment = Payment( type=g.CASH, total=bill["total"], currency=get_company_value(request.user, c, "pos_currency"), transaction_datetime=datetime.utcnow(), status=g.WAITING, created_by=request.user, ) bill_payment.save() # create a new bill db_bill = Bill( created_by=request.user, company=c, # current company, FK to Company object issuer=bill["issuer"], # fixed company details at this moment, FK to BillCompany object user_id=bill["user_id"], # id of user that created this bill, just an integer, not a FK user_name=bill["user_name"], # copied user name in case that user gets 'fired' register=bill["register"], # current settings of the register this bill was created on contact=bill["contact"], # FK on BillContact, copy of the Contact object notes=bill["notes"], # timestamp=dtm.utcnow().replace(tzinfo=timezone(get_company_value(request.user, c, 'pos_timezone'))), timestamp=bill["timestamp"], payment=bill_payment, base=bill["base"], discount=bill["discount"], tax=bill["tax"], ) db_bill.save() # create new items for item in bill["items"]: db_item = BillItem( created_by=item["created_by"], code=item["code"], shortcut=item["shortcut"], name=item["name"], description=item["description"], private_notes=item["private_notes"], unit_type=item["unit_type"], bill=db_bill, bill_notes=item["bill_notes"], product_id=item["product_id"], quantity=item["quantity"], base=item["base"], tax_rate=item["tax_rate"], batch=item["batch"], discount=item["discount"], net=item["net"], tax=item["tax"], total=item["total"], ) db_item.save() # save discounts for this item for discount in item["discounts"]: db_discount = BillItemDiscount( created_by=request.user, bill_item=db_item, description=discount["description"], code=discount["code"], type=discount["type"], amount=discount["amount"], ) db_discount.save() db_bill.save() d = {"bill": bill_to_dict(request.user, c, db_bill)} return JsonOk(extra=d)
def send_invoice_(request, c): data = JsonParse(request.POST.get("data")) # there should be bill_id in request.POST try: bill_id = int(data.get("bill_id")) bill = Bill.objects.get(company=c, id=bill_id) except (Bill.DoesNotExist, ValueError, TypeError): return JsonError(_("Bill does not exist or data is invalid")) if bill.company == c and has_permission(request.user, c, "bill", "edit"): company_paypal_address = get_company_value(request.user, c, "pos_payment_paypal_address") if company_paypal_address == "": company_paypal_address = c.email if bill.status == g.PAID: return JsonResponse({"status": "error", "message": "bill_payment_already_paid"}) merchant_info = { "email": company_paypal_address, "address": {"line1": c.street, "city": c.city, "country_code": c.country, "state": "XX"}, } if c.name and c.name != "": merchant_info["business_name"] = c.name if c.phone and c.phone != "": merchant_info["phone"] = c.phone if c.website and c.website != "": merchant_info["website"] = c.website if c.postcode and c.postcode != "": merchant_info["address"]["postal_code"] = c.postcode if c.vat_no and c.vat_no != "": merchant_info["tax_id"] = c.vat_no customer_email = data.get("customer_email") try: validate_email(customer_email) except ValidationError: return JsonResponse({"status": "error", "message": "invalid_customer_email"}) billing_info = [{"email": customer_email}] shipping_info = None items = [] bill_datetime_format = g.DATE_FORMATS[get_company_value(request.user, c, "pos_date_format")]["python"] bill_datetime = bill.timestamp.strftime(bill_datetime_format) + " UTC" currency = get_company_value(request.user, c, "pos_currency") bill_items = BillItem.objects.filter(bill=bill) for bi in bill_items: try: product = Product.objects.get(id=bi.product_id) item_data = { "name": product.name, "quantity": bi.quantity.quantize(Decimal(".001"), rounding=ROUND_UP), "unit_price": {"currency": currency, "value": bi.base.quantize(Decimal(".01"), rounding=ROUND_UP)}, "tax": { "name": product.tax.name, "percent": bi.tax_rate.quantize(Decimal(".001"), rounding=ROUND_UP), }, "date": bill_datetime, } if product.description and product.description != "": item_data["description"] = product.description if bi.discount and bi.discount is not None: item_data["discount"] = bi.discount items.append(item_data) except Product.DoesNotExist: pass paypal = Paypal() if not paypal.create_invoice( invoice_id=bill.serial, merchant_info=merchant_info, billing_info=billing_info, shipping_info=shipping_info, items=items, invoice_date=bill_datetime, ): return JsonResponse({"status": "error", "message": "could_not_create_invoice"}) payment = bill.payment payment.paypal_transaction_reference = paypal.response["id"] payment.save() if not paypal.send_invoice(): return JsonResponse({"status": "error", "message": "could_not_send_invoice"}) else: return JsonResponse({"status": "error", "message": "trying_to_compromise"}) return JsonOk()