コード例 #1
0
ファイル: stock.py プロジェクト: rokj/sellout
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)
コード例 #2
0
ファイル: signals.py プロジェクト: rokj/sellout
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)
コード例 #3
0
ファイル: functions.py プロジェクト: rokj/sellout
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}
コード例 #4
0
ファイル: configuration.py プロジェクト: rokj/sellout
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')
    }
コード例 #5
0
ファイル: functions.py プロジェクト: rokj/sellout
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)
コード例 #6
0
ファイル: configuration.py プロジェクト: rokj/sellout
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)
コード例 #7
0
ファイル: register.py プロジェクト: rokj/sellout
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)
コード例 #8
0
ファイル: models.py プロジェクト: rokj/sellout
    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
コード例 #9
0
ファイル: tax.py プロジェクト: rokj/sellout
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)
コード例 #10
0
ファイル: stats.py プロジェクト: rokj/sellout
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)
コード例 #11
0
ファイル: stock.py プロジェクト: rokj/sellout
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)
コード例 #12
0
ファイル: bill.py プロジェクト: rokj/sellout
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)
コード例 #13
0
ファイル: discount.py プロジェクト: rokj/sellout
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) 
コード例 #14
0
ファイル: bill.py プロジェクト: rokj/sellout
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)
コード例 #15
0
ファイル: bill.py プロジェクト: rokj/sellout
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()