Merge branch 'develop' of https://github.com/frappe/erpnext into develop

This commit is contained in:
Khushal Trivedi 2019-12-23 15:02:20 +05:30
commit 56819d9389
50 changed files with 670 additions and 216 deletions

View File

@ -389,8 +389,7 @@
"fieldname": "rate_or_discount",
"fieldtype": "Select",
"label": "Rate or Discount",
"options": "\nRate\nDiscount Percentage\nDiscount Amount",
"reqd": 1
"options": "\nRate\nDiscount Percentage\nDiscount Amount"
},
{
"default": "Grand Total",
@ -439,19 +438,20 @@
},
{
"default": "0",
"depends_on": "eval:!doc.mixed_conditions",
"depends_on": "eval:!doc.mixed_conditions && doc.apply_on != 'Transaction'",
"fieldname": "same_item",
"fieldtype": "Check",
"label": "Same Item"
},
{
"depends_on": "eval:!doc.same_item || doc.mixed_conditions",
"depends_on": "eval:(!doc.same_item || doc.apply_on == 'Transaction') || doc.mixed_conditions",
"fieldname": "free_item",
"fieldtype": "Link",
"label": "Free Item",
"options": "Item"
},
{
"default": "0",
"fieldname": "free_qty",
"fieldtype": "Float",
"label": "Qty"
@ -554,7 +554,7 @@
],
"icon": "fa fa-gift",
"idx": 1,
"modified": "2019-10-15 12:39:40.399792",
"modified": "2019-12-18 17:29:22.957077",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@ -47,6 +47,9 @@ class PricingRule(Document):
if tocheck and not self.get(tocheck):
throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError)
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying:
throw(_("Atleast one of the Selling or Buying must be selected"))
@ -182,7 +185,7 @@ def get_serial_no_for_item(args):
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules,
get_applied_pricing_rules, get_pricing_rule_items)
get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule)
if isinstance(doc, string_types):
doc = json.loads(doc)
@ -241,9 +244,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
return item_details
if (not pricing_rule.validate_applied_rule and
pricing_rule.price_or_product_discount == "Price"):
apply_price_discount_pricing_rule(pricing_rule, item_details, args)
if not pricing_rule.validate_applied_rule:
if pricing_rule.price_or_product_discount == "Price":
apply_price_discount_rule(pricing_rule, item_details, args)
else:
get_product_discount_rule(pricing_rule, item_details, doc)
item_details.has_pricing_rule = 1
@ -293,7 +298,7 @@ def get_pricing_rule_details(args, pricing_rule):
'child_docname': args.get('child_docname')
})
def apply_price_discount_pricing_rule(pricing_rule, item_details, args):
def apply_price_discount_rule(pricing_rule, item_details, args):
item_details.pricing_rule_for = pricing_rule.rate_or_discount
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency)

View File

@ -7,7 +7,7 @@ from __future__ import unicode_literals
import frappe, copy, json
from frappe import throw, _
from six import string_types
from frappe.utils import flt, cint, get_datetime
from frappe.utils import flt, cint, get_datetime, get_link_to_form, today
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor
@ -284,7 +284,7 @@ def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None):
status = True
# if user has created item price against the transaction UOM
if rule.get("uom") == args.get("uom"):
if args and rule.get("uom") == args.get("uom"):
conversion_factor = 1.0
if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor)
@ -408,7 +408,8 @@ def apply_pricing_rule_on_transaction(doc):
conditions = get_other_conditions(conditions, values, doc)
pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule`
where {conditions} """.format(conditions = conditions), values, as_dict=1)
where {conditions} and `tabPricing Rule`.disable = 0
""".format(conditions = conditions), values, as_dict=1)
if pricing_rules:
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
@ -420,39 +421,65 @@ def apply_pricing_rule_on_transaction(doc):
doc.set('apply_discount_on', d.apply_discount_on)
for field in ['additional_discount_percentage', 'discount_amount']:
if not d.get(field): continue
pr_field = ('discount_percentage'
if field == 'additional_discount_percentage' else field)
if not d.get(pr_field): continue
if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
frappe.msgprint(_("User has not applied rule on the invoice {0}")
.format(doc.name))
else:
doc.set(field, d.get(pr_field))
doc.calculate_taxes_and_totals()
elif d.price_or_product_discount == 'Product':
apply_pricing_rule_for_free_items(doc, d)
item_details = frappe._dict({'parenttype': doc.doctype})
get_product_discount_rule(d, item_details, doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values()
def get_applied_pricing_rules(item_row):
return (item_row.get("pricing_rules").split(',')
if item_row.get("pricing_rules") else [])
def apply_pricing_rule_for_free_items(doc, pricing_rule):
if pricing_rule.get('free_item'):
def get_product_discount_rule(pricing_rule, item_details, doc=None):
free_item = (pricing_rule.free_item
if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code)
if not free_item:
frappe.throw(_("Free item not set in the pricing rule {0}")
.format(get_link_to_form("Pricing Rule", pricing_rule.name)))
item_details.free_item_data = {
'item_code': free_item,
'qty': pricing_rule.free_qty or 1,
'rate': pricing_rule.free_item_rate or 0,
'price_list_rate': pricing_rule.free_item_rate or 0,
'is_free_item': 1
}
item_data = frappe.get_cached_value('Item', free_item, ['item_name',
'description', 'stock_uom'], as_dict=1)
item_details.free_item_data.update(item_data)
item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
item_details.free_item_data['uom']).get("conversion_factor", 1)
if item_details.get("parenttype") == 'Purchase Order':
item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today()
if item_details.get("parenttype") == 'Sales Order':
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
if pricing_rule_args.get('item_code'):
items = [d.item_code for d in doc.items
if d.item_code == (d.item_code
if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item]
if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
if not items:
doc.append('items', {
'item_code': pricing_rule.get('free_item'),
'qty': pricing_rule.get('free_qty'),
'uom': pricing_rule.get('free_item_uom'),
'rate': pricing_rule.get('free_item_rate') or 0,
'is_free_item': 1
})
doc.set_missing_values()
doc.append('items', pricing_rule_args)
def get_pricing_rule_items(pr_doc):
apply_on_data = []

View File

@ -382,21 +382,11 @@ cur_frm.fields_dict['items'].grid.get_field("item_code").get_query = function(do
cur_frm.fields_dict['credit_to'].get_query = function(doc) {
// filter on Account
if (doc.supplier) {
return {
filters: {
'account_type': 'Payable',
'is_group': 0,
'company': doc.company
}
}
} else {
return {
filters: {
'report_type': 'Balance Sheet',
'is_group': 0,
'company': doc.company
}
return {
filters: {
'account_type': 'Payable',
'is_group': 0,
'company': doc.company
}
}
}

View File

@ -556,22 +556,11 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn) {
}
cur_frm.set_query("debit_to", function(doc) {
// filter on Account
if (doc.customer) {
return {
filters: {
'account_type': 'Receivable',
'is_group': 0,
'company': doc.company
}
}
} else {
return {
filters: {
'report_type': 'Balance Sheet',
'is_group': 0,
'company': doc.company
}
return {
filters: {
'account_type': 'Receivable',
'is_group': 0,
'company': doc.company
}
}
});

View File

@ -188,7 +188,7 @@
}
],
"is_submittable": 1,
"modified": "2019-11-07 13:31:17.999744",
"modified": "2019-12-20 14:48:01.990600",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Share Transfer",
@ -196,6 +196,7 @@
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -221,6 +222,7 @@
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -230,6 +232,7 @@
"report": 1,
"role": "Accounts Manager",
"share": 1,
"submit": 1,
"write": 1
}
],

View File

@ -90,8 +90,12 @@ def merge_similar_entries(gl_map):
else:
merged_gl_map.append(entry)
company = gl_map[0].company if gl_map else erpnext.get_default_company()
company_currency = erpnext.get_company_currency(company)
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
# filter zero debit and credit entries
merged_gl_map = filter(lambda x: flt(x.debit, 9)!=0 or flt(x.credit, 9)!=0, merged_gl_map)
merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
merged_gl_map = list(merged_gl_map)
return merged_gl_map

View File

@ -171,7 +171,7 @@ class ReceivablePayableReport(object):
row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision)
row.invoice_grand_total = row.invoiced
if abs(row.outstanding) > 0.1/10 ** self.currency_precision:
if abs(row.outstanding) > 1.0/10 ** self.currency_precision:
# non-zero oustanding, we must consider this row
if self.is_invoice(row) and self.filters.based_on_payment_terms:

View File

@ -1,5 +1,6 @@
{%
var report_columns = report.get_columns_for_print();
report_columns = report_columns.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application."));
@ -15,34 +16,35 @@
height: 37px;
}
</style>
{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) %}
{% if(letterhead) { %}
<div style="margin-bottom: 7px;" class="text-center">
{%= frappe.boot.letter_heads[letterhead].header %}
</div>
{% } %}
<h2 class="text-center">{%= __(report.report_name) %}</h2>
<h3 class="text-center">{%= filters.company %}</h3>
{% if 'cost_center' in filters %}
<h3 class="text-center">{%= filters.cost_center %}</h3>
{% endif %}
<h3 class="text-center">{%= filters.fiscal_year %}</h3>
<h5 class="text-center">{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %} </h4>
<h5 class="text-center">
{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</h5>
{% if (filters.from_date) { %}
<h4 class="text-center">{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}</h3>
<h5 class="text-center">
{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}
</h5>
{% } %}
<hr>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: {%= 100 - (report_columns.length - 2) * 13 %}%"></th>
{% for(var i=2, l=report_columns.length; i<l; i++) { %}
<th style="width: {%= 100 - (report_columns.length - 1) * 13 %}%"></th>
{% for (let i=1, l=report_columns.length; i<l; i++) { %}
<th class="text-right">{%= report_columns[i].label %}</th>
{% } %}
</tr>
</thead>
<tbody>
{% for(var j=0, k=data.length-1; j<k; j++) { %}
{% for(let j=0, k=data.length-1; j<k; j++) { %}
{%
var row = data[j];
var row_class = data[j].parent_account ? "" : "financial-statements-important";
@ -52,11 +54,11 @@
<td>
<span style="padding-left: {%= cint(data[j].indent) * 2 %}em">{%= row.account_name %}</span>
</td>
{% for(var i=2, l=report_columns.length; i<l; i++) { %}
{% for(let i=1, l=report_columns.length; i<l; i++) { %}
<td class="text-right">
{% var fieldname = report_columns[i].fieldname; %}
{% const fieldname = report_columns[i].fieldname; %}
{% if (!is_null(row[fieldname])) { %}
{%= format_currency(row[fieldname], filters.presentation_currency) %}
{%= frappe.format(row[fieldname], report_columns[i], {}, row) %}
{% } %}
</td>
{% } %}
@ -64,4 +66,6 @@
{% } %}
</tbody>
</table>
<p class="text-right text-muted">Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
<p class="text-right text-muted">
Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
</p>

View File

@ -264,8 +264,8 @@ def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False
def add_total_row(out, root_type, balance_must_be, period_list, company_currency):
total_row = {
"account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
"account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
"account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
"account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
"currency": company_currency
}

View File

@ -38,32 +38,46 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
cost_center = list(set(invoice_cc_wh_map.get(inv.name, {}).get("cost_center", [])))
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
row = [
inv.name, inv.posting_date, inv.customer, inv.customer_name
]
row = {
'invoice': inv.name,
'posting_date': inv.posting_date,
'customer': inv.customer,
'customer_name': inv.customer_name
}
if additional_query_columns:
for col in additional_query_columns:
row.append(inv.get(col))
row.update({
col: inv.get(col)
})
row.update({
'customer_group': inv.get("customer_group"),
'territory': inv.get("territory"),
'tax_id': inv.get("tax_id"),
'receivable_account': inv.debit_to,
'mode_of_payment': ", ".join(mode_of_payments.get(inv.name, [])),
'project': inv.project,
'owner': inv.owner,
'remarks': inv.remarks,
'sales_order': ", ".join(sales_order),
'delivery_note': ", ".join(delivery_note),
'cost_center': ", ".join(cost_center),
'warehouse': ", ".join(warehouse),
'currency': company_currency
})
row +=[
inv.get("customer_group"),
inv.get("territory"),
inv.get("tax_id"),
inv.debit_to, ", ".join(mode_of_payments.get(inv.name, [])),
inv.project, inv.owner, inv.remarks,
", ".join(sales_order), ", ".join(delivery_note),", ".join(cost_center),
", ".join(warehouse), company_currency
]
# map income values
base_net_total = 0
for income_acc in income_accounts:
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
base_net_total += income_amount
row.append(income_amount)
row.update({
frappe.scrub(income_acc): income_amount
})
# net total
row.append(base_net_total or inv.base_net_total)
row.update({'net_total': base_net_total or inv.base_net_total})
# tax account
total_tax = 0
@ -72,10 +86,18 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
tax_amount_precision = get_field_precision(frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency) or 2
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc), tax_amount_precision)
total_tax += tax_amount
row.append(tax_amount)
row.update({
frappe.scrub(tax_acc): tax_amount
})
# total tax, grand total, outstanding amount & rounded total
row += [total_tax, inv.base_grand_total, inv.base_rounded_total, inv.outstanding_amount]
row.update({
'tax_total': total_tax,
'grand_total': inv.base_grand_total,
'rounded_total': inv.base_rounded_total,
'outstanding_amount': inv.outstanding_amount
})
data.append(row)
@ -84,19 +106,118 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
def get_columns(invoice_list, additional_table_columns):
"""return columns based on filters"""
columns = [
_("Invoice") + ":Link/Sales Invoice:120", _("Posting Date") + ":Date:80",
_("Customer") + ":Link/Customer:120", _("Customer Name") + "::120"
{
'label': _("Invoice"),
'fieldname': 'invoice',
'fieldtype': 'Link',
'options': 'Sales Invoice',
'width': 120
},
{
'label': _("Posting Date"),
'fieldname': 'posting_date',
'fieldtype': 'Date',
'width': 80
},
{
'label': _("Customer"),
'fieldname': 'customer',
'fieldtype': 'Link',
'options': 'Customer',
'width': 120
},
{
'label': _("Customer Name"),
'fieldname': 'customer_name',
'fieldtype': 'Data',
'width': 120
},
]
if additional_table_columns:
columns += additional_table_columns
columns +=[
_("Customer Group") + ":Link/Customer Group:120", _("Territory") + ":Link/Territory:80",
_("Tax Id") + "::80", _("Receivable Account") + ":Link/Account:120", _("Mode of Payment") + "::120",
_("Project") +":Link/Project:80", _("Owner") + "::150", _("Remarks") + "::150",
_("Sales Order") + ":Link/Sales Order:100", _("Delivery Note") + ":Link/Delivery Note:100",
_("Cost Center") + ":Link/Cost Center:100", _("Warehouse") + ":Link/Warehouse:100",
{
'label': _("Custmer Group"),
'fieldname': 'customer_group',
'fieldtype': 'Link',
'options': 'Customer Group',
'width': 120
},
{
'label': _("Territory"),
'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
'width': 80
},
{
'label': _("Tax Id"),
'fieldname': 'tax_id',
'fieldtype': 'Data',
'width': 120
},
{
'label': _("Receivable Account"),
'fieldname': 'receivable_account',
'fieldtype': 'Link',
'options': 'Account',
'width': 80
},
{
'label': _("Mode Of Payment"),
'fieldname': 'mode_of_payment',
'fieldtype': 'Data',
'width': 120
},
{
'label': _("Project"),
'fieldname': 'project',
'fieldtype': 'Link',
'options': 'project',
'width': 80
},
{
'label': _("Owner"),
'fieldname': 'owner',
'fieldtype': 'Data',
'width': 150
},
{
'label': _("Remarks"),
'fieldname': 'remarks',
'fieldtype': 'Data',
'width': 150
},
{
'label': _("Sales Order"),
'fieldname': 'sales_order',
'fieldtype': 'Link',
'options': 'Sales Order',
'width': 100
},
{
'label': _("Delivery Note"),
'fieldname': 'delivery_note',
'fieldtype': 'Link',
'options': 'Delivery Note',
'width': 100
},
{
'label': _("Cost Center"),
'fieldname': 'cost_center',
'fieldtype': 'Link',
'options': 'Cost Center',
'width': 100
},
{
'label': _("Warehouse"),
'fieldname': 'warehouse',
'fieldtype': 'Link',
'options': 'Warehouse',
'width': 100
},
{
"fieldname": "currency",
"label": _("Currency"),
@ -105,7 +226,10 @@ def get_columns(invoice_list, additional_table_columns):
}
]
income_accounts = tax_accounts = income_columns = tax_columns = []
income_accounts = []
tax_accounts = []
income_columns = []
tax_columns = []
if invoice_list:
income_accounts = frappe.db.sql_list("""select distinct income_account
@ -119,14 +243,65 @@ def get_columns(invoice_list, additional_table_columns):
and parent in (%s) order by account_head""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
income_columns = [(account + ":Currency/currency:120") for account in income_accounts]
for account in income_accounts:
income_columns.append({
"label": account,
"fieldname": frappe.scrub(account),
"fieldtype": "Currency",
"options": 'currency',
"width": 120
})
for account in tax_accounts:
if account not in income_accounts:
tax_columns.append(account + ":Currency/currency:120")
tax_columns.append({
"label": account,
"fieldname": frappe.scrub(account),
"fieldtype": "Currency",
"options": 'currency',
"width": 120
})
columns = columns + income_columns + [_("Net Total") + ":Currency/currency:120"] + tax_columns + \
[_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120",
_("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"]
net_total_column = [{
"label": _("Net Total"),
"fieldname": "net_total",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
}]
total_columns = [
{
"label": _("Tax Total"),
"fieldname": "tax_total",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
},
{
"label": _("Grand Total"),
"fieldname": "grand_total",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
},
{
"label": _("Rounded Total"),
"fieldname": "rounded_total",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
},
{
"label": _("Outstanding Amount"),
"fieldname": "outstanding_amount",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
}
]
columns = columns + income_columns + net_total_column + tax_columns + total_columns
return columns, income_accounts, tax_accounts

View File

@ -144,6 +144,10 @@ frappe.ui.form.on('Asset', {
frm.set_df_property('purchase_invoice', 'read_only', 1);
frm.set_df_property('purchase_receipt', 'read_only', 1);
}
else if (frm.doc.is_existing_asset) {
frm.toggle_reqd('purchase_receipt', 0);
frm.toggle_reqd('purchase_invoice', 0);
}
else if (frm.doc.purchase_receipt) {
// if purchase receipt link is set then set PI disabled
frm.toggle_reqd('purchase_invoice', 0);
@ -256,6 +260,7 @@ frappe.ui.form.on('Asset', {
},
is_existing_asset: function(frm) {
frm.trigger("toggle_reference_doc");
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
},

View File

@ -12,9 +12,10 @@
}
],
"idx": 0,
"image_src": "/assets/erpnext/images/illustrations/supplier-onboard.png",
"image_src": "",
"is_completed": 0,
"max_count": 3,
"modified": "2019-12-03 22:53:50.552445",
"modified": "2019-12-09 17:54:18.452038",
"modified_by": "Administrator",
"name": "Add A Few Suppliers",
"owner": "Administrator",

View File

@ -319,8 +319,8 @@ class AccountsController(TransactionBase):
if item.get('discount_amount'):
item.rate = item.price_list_rate - item.discount_amount
elif pricing_rule_args.get('free_item'):
apply_pricing_rule_for_free_items(self, pricing_rule_args)
elif pricing_rule_args.get('free_item_data'):
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
elif pricing_rule_args.get("validate_applied_rule"):
for pricing_rule in get_applied_pricing_rules(item):

View File

@ -265,16 +265,17 @@ class BuyingController(StockController):
fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
raw_material_data = backflushed_raw_materials_map.get(item_key, {})
consumed_qty = raw_material_data.get('qty', 0)
consumed_serial_nos = raw_material_data.get('serial_nos', '')
consumed_batch_nos = raw_material_data.get('batch_nos', '')
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
for raw_material in transferred_raw_materials + non_stock_items:
rm_item_key = '{}{}'.format(raw_material.rm_item_code, item.purchase_order)
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
consumed_qty = raw_material_data.get('qty', 0)
consumed_serial_nos = raw_material_data.get('serial_nos', '')
consumed_batch_nos = raw_material_data.get('batch_nos', '')
transferred_qty = raw_material.qty
rm_qty_to_be_consumed = transferred_qty - consumed_qty

View File

@ -180,6 +180,7 @@ standard_portal_menu_items = [
{"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"},
{"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application", "role": "Non Profit Portal User"},
{"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"},
{"title": _("Appointment Booking"), "route": "/book_appointment"},
]
default_roles = [

View File

@ -1,9 +1,44 @@
<h3>{{_("Training Event")}}</h3>
<table class="panel-header" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr height="10"></tr>
<tr>
<td width="15"></td>
<td>
<div class="text-medium text-muted">
<span>{{_("Training Event:")}} {{ doc.event_name }}</span>
</div>
</td>
<td width="15"></td>
</tr>
<tr height="10"></tr>
</table>
<p>{{ doc.introduction }}</p>
<h4>{{_("Details")}}</h4>
{{_("Event Name")}}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
<br>{{_("Event Location")}}: {{ doc.location }}
<br>{{_("Start Time")}}: {{ doc.start_time }}
<br>{{_("End Time")}}: {{ doc.end_time }}
<table class="panel-body" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr height="10"></tr>
<tr>
<td width="15"></td>
<td>
<div>
{{ doc.introduction }}
<ul class="list-unstyled" style="line-height: 1.7">
<li>{{_("Event Location")}}: <b>{{ doc.location }}</b></li>
{% set start = frappe.utils.get_datetime(doc.start_time) %}
{% set end = frappe.utils.get_datetime(doc.end_time) %}
{% if start.date() == end.date() %}
<li>{{_("Date")}}: <b>{{ start.strftime("%A, %d %b %Y") }}</b></li>
<li>
{{_("Timing")}}: <b>{{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }}</b>
</li>
{% else %}
<li>{{_("Start Time")}}: <b>{{ start.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
</li>
<li>{{_("End Time")}}: <b>{{ end.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
</li>
{% endif %}
<li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
</ul>
</div>
</td>
<td width="15"></td>
</tr>
<tr height="10"></tr>
</table>

View File

@ -1,5 +1,7 @@
{
"attach_print": 0,
"channel": "Email",
"condition": "",
"creation": "2017-08-11 03:13:40.519614",
"days_in_advance": 0,
"docstatus": 0,
@ -9,8 +11,8 @@
"event": "Submit",
"idx": 0,
"is_standard": 1,
"message": "<h3>{{_(\"Training Event\")}}</h3>\n\n<p>{{ doc.introduction }}</p>\n\n<h4>{{_(\"Details\")}}</h4>\n{{_(\"Event Name\")}}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n<br>{{_(\"Event Location\")}}: {{ doc.location }}\n<br>{{_(\"Start Time\")}}: {{ doc.start_time }}\n<br>{{_(\"End Time\")}}: {{ doc.end_time }}\n",
"modified": "2017-08-13 22:49:42.338881",
"message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{ doc.introduction }}</li>\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n {% endif %}\n </ul>\n {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
"modified": "2019-11-29 15:38:31.805409",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Scheduled",

View File

@ -1,9 +1,44 @@
<h3>{{_("Training Event")}}</h3>
<p>{{ message }}</p>
<table class="panel-header" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr height="10"></tr>
<tr>
<td width="15"></td>
<td>
<div class="text-medium text-muted">
<span>{{_("Training Event:")}} {{ doc.event_name }}</span>
</div>
</td>
<td width="15"></td>
</tr>
<tr height="10"></tr>
</table>
<h4>{{_("Details")}}</h4>
{{_("Event Name")}}: <a href="{{ event_link }}">{{ name }}</a>
<br>{{_("Event Location")}}: {{ location }}
<br>{{_("Start Time")}}: {{ start_time }}
<br>{{_("End Time")}}: {{ end_time }}
<br>{{_("Attendance")}}: {{ attendance }}
<table class="panel-body" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr height="10"></tr>
<tr>
<td width="15"></td>
<td>
<div>
{{ doc.introduction }}
<ul class="list-unstyled" style="line-height: 1.7">
<li>{{_("Event Location")}}: <b>{{ doc.location }}</b></li>
{% set start = frappe.utils.get_datetime(doc.start_time) %}
{% set end = frappe.utils.get_datetime(doc.end_time) %}
{% if start.date() == end.date() %}
<li>{{_("Date")}}: <b>{{ start.strftime("%A, %d %b %Y") }}</b></li>
<li>
{{_("Timing")}}: <b>{{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }}</b>
</li>
{% else %}
<li>{{_("Start Time")}}: <b>{{ start.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
</li>
<li>{{_("End Time")}}: <b>{{ end.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
</li>
{% endif %}
<li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
</ul>
</div>
</td>
<td width="15"></td>
</tr>
<tr height="10"></tr>
</table>

View File

@ -65,6 +65,7 @@ class BOM(WebsiteGenerator):
context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
def on_update(self):
frappe.cache().hdel('bom_children', self.name)
self.check_recursion()
self.update_stock_qty()
self.update_exploded_items()

View File

@ -605,6 +605,8 @@ erpnext.work_order = {
description: __('Max: {0}', [max]),
default: max
}, data => {
max += (max * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
if (data.qty > max) {
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
reject();

View File

@ -38,7 +38,7 @@ class WorkOrder(Document):
ms = frappe.get_doc("Manufacturing Settings")
self.set_onload("material_consumption", ms.material_consumption)
self.set_onload("backflush_raw_materials_based_on", ms.backflush_raw_materials_based_on)
self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order)
def validate(self):
self.validate_production_item()
@ -657,8 +657,9 @@ def make_work_order(item, qty=0, project=None):
wo_doc = frappe.new_doc("Work Order")
wo_doc.production_item = item
wo_doc.update(item_details)
if qty > 0:
wo_doc.qty = qty
if flt(qty) > 0:
wo_doc.qty = flt(qty)
wo_doc.get_items_and_operations_from_bom()
return wo_doc

View File

@ -27,6 +27,8 @@ def execute():
tax_category = inter_state_category.name
for doctype in ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template'):
if not frappe.get_meta(doctype).has_field('is_inter_state'): continue
template = frappe.db.get_value(doctype, {'is_inter_state': 1, 'disabled': 0}, ['name'])
if template:
frappe.db.set_value(doctype, template, 'tax_category', tax_category)

View File

@ -7,6 +7,8 @@ def execute():
if not company:
return
frappe.reload_doc('accounts', 'doctype', 'Tax Category')
make_custom_fields()
for doctype in ['Sales Invoice', 'Purchase Invoice']:

View File

@ -52,7 +52,6 @@ def get_attribute_filter_data():
def get_products_for_website(field_filters=None, attribute_filters=None, search=None):
if attribute_filters:
item_codes = get_item_codes_by_attributes(attribute_filters)
items_by_attributes = get_items([['name', 'in', item_codes]])
@ -302,6 +301,8 @@ def get_items(filters=None, search=None):
if isinstance(filters, dict):
filters = [['Item', fieldname, '=', value] for fieldname, value in filters.items()]
enabled_items_filter = get_conditions({ 'disabled': 0 }, 'and')
show_in_website_condition = ''
if products_settings.hide_variants:
show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and')
@ -337,7 +338,8 @@ def get_items(filters=None, search=None):
filter_condition = get_conditions(filters, 'and')
where_conditions = ' and '.join(
[condition for condition in [show_in_website_condition, search_condition, filter_condition] if condition]
[condition for condition in [enabled_items_filter, show_in_website_condition, \
search_condition, filter_condition] if condition]
)
left_joins = []

View File

@ -47,11 +47,11 @@ class Task(NestedSet):
if not self.project or frappe.flags.in_test:
return
expected_end_date = getdate(frappe.db.get_value("Project", self.project, "expected_end_date"))
expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date")
if expected_end_date:
validate_project_dates(expected_end_date, self, "exp_start_date", "exp_end_date", "Expected")
validate_project_dates(expected_end_date, self, "act_start_date", "act_end_date", "Actual")
validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected")
validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual")
def validate_status(self):
if self.status!=self.get_db_value("status") and self.status == "Completed":
@ -278,4 +278,4 @@ def validate_project_dates(project_end_date, task, task_start, task_end, actual_
frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date))
if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))
frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -500,6 +500,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
() => {
var d = locals[cdt][cdn];
me.add_taxes_from_item_tax_template(d.item_tax_rate);
if (d.free_item_data) {
me.apply_product_discount(d.free_item_data);
}
},
() => me.frm.script_manager.trigger("price_list_rate", cdt, cdn),
() => me.toggle_conversion_factor(item),
@ -1305,6 +1308,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
me.remove_pricing_rule(frappe.get_doc(d.doctype, d.name));
}
if (d.free_item_data) {
me.apply_product_discount(d.free_item_data);
}
if (d.apply_rule_on_other_items) {
items_rule_dict[d.name] = d;
}
@ -1334,6 +1341,20 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}
},
apply_product_discount: function(free_item_data) {
const items = this.frm.doc.items.filter(d => (d.item_code == free_item_data.item_code
&& d.is_free_item)) || [];
if (!items.length) {
let row_to_modify = frappe.model.add_child(this.frm.doc,
this.frm.doc.doctype + ' Item', 'items');
for (let key in free_item_data) {
row_to_modify[key] = free_item_data[key];
}
}
},
apply_price_list: function(item, reset_plc_conversion) {
// We need to reset plc_conversion_rate sometimes because the call to
// `erpnext.stock.get_item_details.apply_price_list` is sensitive to its value

View File

@ -44,12 +44,16 @@ class Gstr2Report(Gstr1Report):
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
invoice_details = self.invoices.get(inv)
for rate, items in items_based_on_rate.items():
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
tax_amount = taxable_value * rate / 100
if inv in self.igst_invoices:
row += [tax_amount, 0, 0]
if inv not in self.igst_invoices:
rate = rate / 2
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
tax_amount = taxable_value * rate / 100
row += [0, tax_amount, tax_amount]
else:
row += [0, tax_amount / 2, tax_amount / 2]
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
tax_amount = taxable_value * rate / 100
row += [tax_amount, 0, 0]
row += [
self.invoice_cess.get(inv),

View File

@ -5,13 +5,13 @@ frappe.ui.form.on("Customer", {
setup: function(frm) {
frm.make_methods = {
'Quotation': () => erpnext.utils.create_new_doc('Quotation', {
'quotation_to': frm.doc.doctype,
'party_name': frm.doc.name
'Quotation': () => frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.customer.customer.make_quotation",
frm: cur_frm
}),
'Opportunity': () => erpnext.utils.create_new_doc('Opportunity', {
'opportunity_from': frm.doc.doctype,
'party_name': frm.doc.name
'Opportunity': () => frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.customer.customer.make_opportunity",
frm: cur_frm
})
}

View File

@ -12,6 +12,7 @@ from erpnext.utilities.transaction_base import TransactionBase
from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this
from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address
from frappe.model.rename_doc import update_linked_doctypes
from frappe.model.mapper import get_mapped_doc
class Customer(TransactionBase):
def get_feed(self):
@ -238,6 +239,66 @@ def create_contact(contact, party_type, party, email):
contact.append('links', dict(link_doctype=party_type, link_name=party))
contact.insert()
@frappe.whitelist()
def make_quotation(source_name, target_doc=None):
def set_missing_values(source, target):
_set_missing_values(source, target)
target_doc = get_mapped_doc("Customer", source_name,
{"Customer": {
"doctype": "Quotation",
"field_map": {
"name":"party_name"
}
}}, target_doc, set_missing_values)
target_doc.quotation_to = "Customer"
target_doc.run_method("set_missing_values")
target_doc.run_method("set_other_charges")
target_doc.run_method("calculate_taxes_and_totals")
price_list = frappe.get_value("Customer", source_name, 'default_price_list')
if price_list:
target_doc.selling_price_list = price_list
return target_doc
@frappe.whitelist()
def make_opportunity(source_name, target_doc=None):
def set_missing_values(source, target):
_set_missing_values(source, target)
target_doc = get_mapped_doc("Customer", source_name,
{"Customer": {
"doctype": "Opportunity",
"field_map": {
"name": "party_name",
"doctype": "opportunity_from",
}
}}, target_doc, set_missing_values)
return target_doc
def _set_missing_values(source, target):
address = frappe.get_all('Dynamic Link', {
'link_doctype': source.doctype,
'link_name': source.name,
'parenttype': 'Address',
}, ['parent'], limit=1)
contact = frappe.get_all('Dynamic Link', {
'link_doctype': source.doctype,
'link_name': source.name,
'parenttype': 'Contact',
}, ['parent'], limit=1)
if address:
target.customer_address = address[0].parent
if contact:
target.contact_person = contact[0].parent
@frappe.whitelist()
def get_loyalty_programs(doc):
''' returns applicable loyalty programs for a customer '''

View File

@ -12,9 +12,10 @@
}
],
"idx": 0,
"image_src": "/assets/erpnext/images/illustrations/customers-onboard.png",
"image_src": "",
"is_completed": 0,
"max_count": 3,
"modified": "2019-12-03 22:54:28.959549",
"modified": "2019-12-09 17:54:01.686006",
"modified_by": "Administrator",
"name": "Add A Few Customers",
"owner": "Administrator",

View File

@ -286,14 +286,14 @@ erpnext.pos.PointOfSale = class PointOfSale {
if (in_list(['serial_no', 'batch_no'], field)) {
args[field] = value;
}
// add to cur_frm
const item = this.frm.add_child('items', args);
frappe.flags.hide_serial_batch_dialog = true;
frappe.run_serially([
() => {
this.frm.script_manager.trigger('item_code', item.doctype, item.name)
return this.frm.script_manager.trigger('item_code', item.doctype, item.name)
.then(() => {
this.frm.script_manager.trigger('qty', item.doctype, item.name)
.then(() => {

View File

@ -14,6 +14,7 @@ from frappe.model.document import Document
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.utils.nestedset import NestedSet
from past.builtins import cmp
import functools
class Company(NestedSet):

View File

@ -106,7 +106,10 @@ def delete_lead_addresses(company_name):
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
def delete_communications(doctype, company_name, company_fieldname):
frappe.db.sql("""
DELETE FROM `tabCommunication` WHERE reference_doctype = %s AND
EXISTS (SELECT name FROM `tab{0}` WHERE {1} = %s AND `tabCommunication`.reference_name = name)
""".format(doctype, company_fieldname), (doctype, company_name))
reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name})
reference_doc_names = [r.name for r in reference_docs]
communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]})
communication_names = [c.name for c in communications]
frappe.delete_doc("Communication", communication_names)

View File

@ -88,6 +88,57 @@ class TestCompany(unittest.TestCase):
self.delete_mode_of_payment(template)
frappe.delete_doc("Company", template)
def test_delete_communication(self):
from erpnext.setup.doctype.company.delete_company_transactions import delete_communications
company = create_child_company()
lead = create_test_lead_in_company(company)
communication = create_company_communication("Lead", lead)
delete_communications("Lead", "Test Company", "company")
self.assertFalse(frappe.db.exists("Communcation", communication))
self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication}))
def delete_mode_of_payment(self, company):
frappe.db.sql(""" delete from `tabMode of Payment Account`
where company =%s """, (company))
def create_company_communication(doctype, docname):
comm = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"content": "Deduplication of Links",
"communication_medium": "Email",
"reference_doctype":doctype,
"reference_name":docname
})
comm.insert()
def create_child_company():
child_company = frappe.db.exists("Company", "Test Company")
if not child_company:
child_company = frappe.get_doc({
"doctype":"Company",
"company_name":"Test Company",
"abbr":"test_company",
"default_currency":"INR"
})
child_company.insert()
else:
child_company = frappe.get_doc("Company", child_company)
return child_company.name
def create_test_lead_in_company(company):
lead = frappe.db.exists("Lead", "Test Lead in new company")
if not lead:
lead = frappe.get_doc({
"doctype": "Lead",
"lead_name": "Test Lead in new company",
"scompany": company
})
lead.insert()
else:
lead = frappe.get_doc("Lead", lead)
lead.company = company
lead.save()
return lead.name

View File

@ -7,10 +7,10 @@
"domains": [],
"help_links": [],
"idx": 0,
"image_src": "/assets/erpnext/images/illustrations/desk-onboard.png",
"image_src": "",
"is_completed": 0,
"max_count": 3,
"modified": "2019-12-04 19:21:39.995776",
"modified": "2019-12-09 17:53:53.849953",
"modified_by": "Administrator",
"name": "Welcome back to ERPNext!",
"owner": "Administrator",

View File

@ -7,13 +7,14 @@
"domains": [],
"help_links": [],
"idx": 0,
"image_src": "/assets/erpnext/images/illustrations/desk-onboard.png",
"image_src": "",
"is_completed": 0,
"max_count": 0,
"modified": "2019-12-03 22:49:12.871260",
"modified": "2019-12-22 21:26:28.414597",
"modified_by": "Administrator",
"name": "Welcome to ERPNext!",
"owner": "Administrator",
"slide_desc": "Setting up an ERP can be overwhelming. But don't worry, we have got your back!\nLet's setup your company.\nThis wizard will help you onboard to ERPNext in a short time!",
"slide_desc": "<div class=\"text center\">Setting up an ERP can be overwhelming. But don't worry, we have got your back! This wizard will help you onboard to ERPNext in a short time!</div>",
"slide_fields": [],
"slide_module": "Setup",
"slide_order": 1,

View File

@ -135,8 +135,7 @@
"publish_in_hub",
"hub_category_to_publish",
"hub_warehouse",
"synced_with_hub",
"manufacturers"
"synced_with_hub"
],
"fields": [
{
@ -1016,12 +1015,6 @@
"label": "Synced With Hub",
"read_only": 1
},
{
"fieldname": "manufacturers",
"fieldtype": "Table",
"label": "Manufacturers",
"options": "Item Manufacturer"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "over_delivery_receipt_allowance",
@ -1049,7 +1042,7 @@
"idx": 2,
"image_field": "image",
"max_attachments": 1,
"modified": "2019-10-09 17:05:59.576119",
"modified": "2019-12-13 12:15:56.197246",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@ -125,7 +125,6 @@ class Item(WebsiteGenerator):
self.validate_auto_reorder_enabled_in_stock_settings()
self.cant_change()
self.update_show_in_website()
self.validate_manufacturer()
if not self.get("__islocal"):
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@ -145,13 +144,6 @@ class Item(WebsiteGenerator):
if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')):
self.description = clean_html(self.description)
def validate_manufacturer(self):
list_man = [(x.manufacturer, x.manufacturer_part_no) for x in self.get('manufacturers')]
set_man = set(list_man)
if len(list_man) != len(set_man):
frappe.throw(_("Duplicate entry in Manufacturers table"))
def validate_customer_provided_part(self):
if self.is_customer_provided_item:
if self.is_purchase_item:

View File

@ -622,7 +622,7 @@ frappe.ui.form.on('Stock Entry Detail', {
if(r.message) {
var d = locals[cdt][cdn];
$.each(r.message, function(k, v) {
d[k] = v;
frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered
});
refresh_field("items");
erpnext.stock.select_batch_and_serial_no(frm, d);

View File

@ -372,7 +372,7 @@ class StockEntry(StockController):
elif d.t_warehouse and not d.basic_rate:
d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
self.doctype, self.name, d.allow_zero_valuation_rate,
currency=erpnext.get_company_currency(self.company))
currency=erpnext.get_company_currency(self.company), company=self.company)
def set_actual_qty(self):
allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
@ -649,6 +649,12 @@ class StockEntry(StockController):
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse])
divide_based_on = total_basic_amount
if self.get("additional_costs") and not total_basic_amount:
# if total_basic_amount is 0, distribute additional charges based on qty
divide_based_on = sum(item.qty for item in list(self.get("items")))
item_account_wise_additional_cost = {}
for t in self.get("additional_costs"):
@ -656,8 +662,11 @@ class StockEntry(StockController):
if d.t_warehouse:
item_account_wise_additional_cost.setdefault((d.item_code, d.name), {})
item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, 0.0)
multiply_based_on = d.basic_amount if total_basic_amount else d.qty
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \
(t.amount * d.basic_amount) / total_basic_amount
(t.amount * multiply_based_on) / divide_based_on
if item_account_wise_additional_cost:
for d in self.get("items"):

View File

@ -790,6 +790,50 @@ class TestStockEntry(unittest.TestCase):
filters={"voucher_type": "Stock Entry", "voucher_no": mr.name}, fieldname="is_opening")
self.assertEqual(is_opening, "Yes")
def test_total_basic_amount_zero(self):
se = frappe.get_doc({"doctype":"Stock Entry",
"purpose":"Material Receipt",
"stock_entry_type":"Material Receipt",
"posting_date": nowdate(),
"company":"_Test Company with perpetual inventory",
"items":[
{
"item_code":"Basil Leaves",
"description":"Basil Leaves",
"qty": 1,
"basic_rate": 0,
"uom":"Nos",
"t_warehouse": "Stores - TCP1",
"allow_zero_valuation_rate": 1,
"cost_center": "Main - TCP1"
},
{
"item_code":"Basil Leaves",
"description":"Basil Leaves",
"qty": 2,
"basic_rate": 0,
"uom":"Nos",
"t_warehouse": "Stores - TCP1",
"allow_zero_valuation_rate": 1,
"cost_center": "Main - TCP1"
},
],
"additional_costs":[
{"expense_account":"Miscellaneous Expenses - TCP1",
"amount":100,
"description": "miscellanous"}
]
})
se.insert()
se.submit()
self.check_gl_entries("Stock Entry", se.name,
sorted([
["Stock Adjustment - TCP1", 100.0, 0.0],
["Miscellaneous Expenses - TCP1", 0.0, 100.0]
])
)
def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
se = frappe.copy_doc(test_records[0])
se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series"

View File

@ -7,9 +7,10 @@
"domains": [],
"help_links": [],
"idx": 0,
"image_src": "/assets/erpnext/images/illustrations/products-onboard.png",
"image_src": "",
"is_completed": 0,
"max_count": 3,
"modified": "2019-12-03 22:54:07.558632",
"modified": "2019-12-09 17:54:09.602885",
"modified_by": "Administrator",
"name": "Add A Few Products You Buy Or Sell",
"owner": "Administrator",

View File

@ -24,20 +24,15 @@ async function get_global_variables() {
}
function setup_timezone_selector() {
/**
* window.timezones is a dictionary with the following structure
* { IANA name: Pretty name}
* For example : { Asia/Kolkata : "India Time - Asia/Kolkata"}
*/
let timezones_element = document.getElementById('appointment-timezone');
let offset = new Date().getTimezoneOffset();
Object.keys(window.timezones).forEach((timezone) => {
let local_timezone = moment.tz.guess()
window.timezones.forEach(timezone => {
let opt = document.createElement('option');
opt.value = timezone;
if (timezone == moment.tz.guess()) {
if (timezone == local_timezone) {
opt.selected = true;
}
opt.innerHTML = window.timezones[timezone]
opt.innerHTML = timezone;
timezones_element.appendChild(opt)
});
}
@ -114,7 +109,7 @@ function get_timeslot_div_layout(timeslot) {
timeslot_div.classList.add('unavailable')
}
timeslot_div.innerHTML = get_slot_layout(start_time);
timeslot_div.id = timeslot.time.substr(11, 20);
timeslot_div.id = timeslot.time.substring(11, 19);
timeslot_div.addEventListener('click', select_time);
return timeslot_div
}

View File

@ -25,18 +25,8 @@ def get_appointment_settings():
@frappe.whitelist(allow_guest=True)
def get_timezones():
from babel.dates import get_timezone, get_timezone_name, Locale
from frappe.utils.momentjs import get_all_timezones
translated_dict = {}
locale = Locale.parse(frappe.local.lang, sep="-")
for tz in get_all_timezones():
timezone_name = get_timezone_name(get_timezone(tz), locale=locale, width='short')
if timezone_name:
translated_dict[tz] = timezone_name + ' - ' + tz
return translated_dict
import pytz
return pytz.all_timezones
@frappe.whitelist(allow_guest=True)
def get_appointment_slots(date, timezone):
@ -90,7 +80,7 @@ def get_available_slots_between(query_start_time, query_end_time, settings):
@frappe.whitelist(allow_guest=True)
def create_appointment(date, time, tz, contact):
format_string = '%Y-%m-%d %H:%M:%S%z'
format_string = '%Y-%m-%d %H:%M:%S'
scheduled_time = datetime.datetime.strptime(date + " " + time, format_string)
# Strip tzinfo from datetime objects since it's handled by the doctype
scheduled_time = scheduled_time.replace(tzinfo = None)