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

This commit is contained in:
Deepesh Garg 2022-06-11 21:56:24 +05:30
commit eb93564db6
65 changed files with 1154 additions and 708 deletions

View File

@ -1,7 +1,6 @@
{ {
"actions": [], "actions": [],
"allow_rename": 1, "allow_rename": 1,
"autoname": "format:PLE-{YY}-{MM}-{######}",
"creation": "2022-05-09 19:35:03.334361", "creation": "2022-05-09 19:35:03.334361",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@ -138,11 +137,10 @@
"in_create": 1, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2022-05-19 18:04:44.609115", "modified": "2022-05-30 19:04:55.532171",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Ledger Entry", "name": "Payment Ledger Entry",
"naming_rule": "Expression",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@ -54,8 +54,8 @@ class PeriodClosingVoucher(AccountsController):
pce = frappe.db.sql( pce = frappe.db.sql(
"""select name from `tabPeriod Closing Voucher` """select name from `tabPeriod Closing Voucher`
where posting_date > %s and fiscal_year = %s and docstatus = 1""", where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
(self.posting_date, self.fiscal_year), (self.posting_date, self.fiscal_year, self.company),
) )
if pce and pce[0][0]: if pce and pce[0][0]:
frappe.throw( frappe.throw(

View File

@ -1790,6 +1790,8 @@
"width": "50%" "width": "50%"
}, },
{ {
"fetch_from": "sales_partner.commission_rate",
"fetch_if_empty": 1,
"fieldname": "commission_rate", "fieldname": "commission_rate",
"fieldtype": "Float", "fieldtype": "Float",
"hide_days": 1, "hide_days": 1,
@ -2038,7 +2040,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2022-03-08 16:08:53.517903", "modified": "2022-06-10 03:52:51.409913",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -13,7 +13,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts", "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"modified": "2022-01-18 18:35:52.326688", "modified": "2022-06-07 14:29:21.352132",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts", "name": "Accounts",

View File

@ -1,8 +1,8 @@
{ {
"action": "Watch Video", "action": "Go to Page",
"action_label": "Learn more about Chart of Accounts", "action_label": "Learn more about Chart of Accounts",
"callback_message": "You can continue with the onboarding after exploring this page", "callback_message": "You can continue with the onboarding after exploring this page",
"callback_title": "Awesome Work", "callback_title": "Explore Chart of Accounts",
"creation": "2020-05-13 19:58:20.928127", "creation": "2020-05-13 19:58:20.928127",
"description": "# Chart Of Accounts\n\nERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to business and legal requirements.", "description": "# Chart Of Accounts\n\nERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to business and legal requirements.",
"docstatus": 0, "docstatus": 0,
@ -12,7 +12,7 @@
"is_complete": 0, "is_complete": 0,
"is_single": 0, "is_single": 0,
"is_skipped": 0, "is_skipped": 0,
"modified": "2021-08-13 11:46:25.878506", "modified": "2022-06-07 14:21:26.264769",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Chart of Accounts", "name": "Chart of Accounts",
"owner": "Administrator", "owner": "Administrator",

View File

@ -2,14 +2,14 @@
"action": "Create Entry", "action": "Create Entry",
"action_label": "Manage Sales Tax Templates", "action_label": "Manage Sales Tax Templates",
"creation": "2020-05-13 19:29:43.844463", "creation": "2020-05-13 19:29:43.844463",
"description": "# Setting up Taxes\n\nERPNext lets you configure your taxes so that they are automatically applied in your buying and selling transactions. You can configure them globally or even on Items. ERPNext taxes are pre-configured for most regions.\n", "description": "# Setting up Taxes\n\nERPNext lets you configure your taxes so that they are automatically applied in your buying and selling transactions. You can configure them globally or even on Items. ERPNext taxes are pre-configured for most regions.\n\n[Checkout pre-configured taxes](/app/sales-taxes-and-charges-template)\n",
"docstatus": 0, "docstatus": 0,
"doctype": "Onboarding Step", "doctype": "Onboarding Step",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"is_single": 0, "is_single": 0,
"is_skipped": 0, "is_skipped": 0,
"modified": "2021-08-13 11:48:37.238610", "modified": "2022-06-07 14:27:15.906286",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Setup Taxes", "name": "Setup Taxes",
"owner": "Administrator", "owner": "Administrator",

View File

@ -42,7 +42,7 @@
{% if(filters.show_future_payments) { %} {% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop(); {% var balance_row = data.slice(-1).pop();
var start = filters.based_on_payment_terms ? 13 : 11; var start = report.columns.findIndex((elem) => (elem.fieldname == 'age'));
var range1 = report.columns[start].label; var range1 = report.columns[start].label;
var range2 = report.columns[start+1].label; var range2 = report.columns[start+1].label;
var range3 = report.columns[start+2].label; var range3 = report.columns[start+2].label;

View File

@ -50,7 +50,15 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": frappe.defaults.get_user_default("fiscal_year"),
"reqd": 1 "reqd": 1,
on_change: () => {
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) {
let year_start_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), "year_start_date");
frappe.query_report.set_filter_value({
period_start_date: year_start_date
});
});
}
}, },
{ {
"fieldname":"to_fiscal_year", "fieldname":"to_fiscal_year",
@ -58,7 +66,15 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": frappe.defaults.get_user_default("fiscal_year"),
"reqd": 1 "reqd": 1,
on_change: () => {
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) {
let year_end_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), "year_end_date");
frappe.query_report.set_filter_value({
period_end_date: year_end_date
});
});
}
}, },
{ {
"fieldname":"finance_book", "fieldname":"finance_book",

View File

@ -35,7 +35,7 @@ frappe.query_reports["Gross Profit"] = {
"fieldname":"group_by", "fieldname":"group_by",
"label": __("Group By"), "label": __("Group By"),
"fieldtype": "Select", "fieldtype": "Select",
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject", "options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nMonthly\nPayment Term",
"default": "Invoice" "default": "Invoice"
}, },
], ],

View File

@ -4,7 +4,7 @@
import frappe import frappe
from frappe import _, scrub from frappe import _, scrub
from frappe.utils import cint, flt from frappe.utils import cint, flt, formatdate
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.utils import get_incoming_rate
@ -124,6 +124,23 @@ def execute(filters=None):
"gross_profit", "gross_profit",
"gross_profit_percent", "gross_profit_percent",
], ],
"monthly": [
"monthly",
"qty",
"base_rate",
"buying_rate",
"base_amount",
"buying_amount",
"gross_profit",
"gross_profit_percent",
],
"payment_term": [
"payment_term",
"base_amount",
"buying_amount",
"gross_profit",
"gross_profit_percent",
],
} }
) )
@ -317,6 +334,19 @@ def get_columns(group_wise_columns, filters):
"options": "territory", "options": "territory",
"width": 100, "width": 100,
}, },
"monthly": {
"label": _("Monthly"),
"fieldname": "monthly",
"fieldtype": "Data",
"width": 100,
},
"payment_term": {
"label": _("Payment Term"),
"fieldname": "payment_term",
"fieldtype": "Link",
"options": "Payment Term",
"width": 170,
},
} }
) )
@ -390,6 +420,9 @@ class GrossProfitGenerator(object):
buying_amount = 0 buying_amount = 0
for row in reversed(self.si_list): for row in reversed(self.si_list):
if self.filters.get("group_by") == "Monthly":
row.monthly = formatdate(row.posting_date, "MMM YYYY")
if self.skip_row(row): if self.skip_row(row):
continue continue
@ -445,17 +478,7 @@ class GrossProfitGenerator(object):
def get_average_rate_based_on_group_by(self): def get_average_rate_based_on_group_by(self):
for key in list(self.grouped): for key in list(self.grouped):
if self.filters.get("group_by") != "Invoice": if self.filters.get("group_by") == "Invoice":
for i, row in enumerate(self.grouped[key]):
if i == 0:
new_row = row
else:
new_row.qty += flt(row.qty)
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
new_row.base_amount += flt(row.base_amount, self.currency_precision)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)
else:
for i, row in enumerate(self.grouped[key]): for i, row in enumerate(self.grouped[key]):
if row.indent == 1.0: if row.indent == 1.0:
if ( if (
@ -469,6 +492,44 @@ class GrossProfitGenerator(object):
if flt(row.qty) or row.base_amount: if flt(row.qty) or row.base_amount:
row = self.set_average_rate(row) row = self.set_average_rate(row)
self.grouped_data.append(row) self.grouped_data.append(row)
elif self.filters.get("group_by") == "Payment Term":
for i, row in enumerate(self.grouped[key]):
invoice_portion = 0
if row.is_return:
invoice_portion = 100
elif row.invoice_portion:
invoice_portion = row.invoice_portion
else:
invoice_portion = row.payment_amount * 100 / row.base_net_amount
if i == 0:
new_row = row
self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion)
else:
new_row.qty += flt(row.qty)
self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion, True)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)
else:
for i, row in enumerate(self.grouped[key]):
if i == 0:
new_row = row
else:
new_row.qty += flt(row.qty)
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
new_row.base_amount += flt(row.base_amount, self.currency_precision)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)
def set_average_based_on_payment_term_portion(self, new_row, row, invoice_portion, aggr=False):
cols = ["base_amount", "buying_amount", "gross_profit"]
for col in cols:
if aggr:
new_row[col] += row[col] * invoice_portion / 100
else:
new_row[col] = row[col] * invoice_portion / 100
def is_not_invoice_row(self, row): def is_not_invoice_row(self, row):
return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get( return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get(
@ -622,6 +683,20 @@ class GrossProfitGenerator(object):
sales_person_cols = "" sales_person_cols = ""
sales_team_table = "" sales_team_table = ""
if self.filters.group_by == "Payment Term":
payment_term_cols = """,if(`tabSales Invoice`.is_return = 1,
'{0}',
coalesce(schedule.payment_term, '{1}')) as payment_term,
schedule.invoice_portion,
schedule.payment_amount """.format(
_("Sales Return"), _("No Terms")
)
payment_term_table = """ left join `tabPayment Schedule` schedule on schedule.parent = `tabSales Invoice`.name and
`tabSales Invoice`.is_return = 0 """
else:
payment_term_cols = ""
payment_term_table = ""
if self.filters.get("sales_invoice"): if self.filters.get("sales_invoice"):
conditions += " and `tabSales Invoice`.name = %(sales_invoice)s" conditions += " and `tabSales Invoice`.name = %(sales_invoice)s"
@ -644,10 +719,12 @@ class GrossProfitGenerator(object):
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return, `tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
`tabSales Invoice Item`.cost_center `tabSales Invoice Item`.cost_center
{sales_person_cols} {sales_person_cols}
{payment_term_cols}
from from
`tabSales Invoice` inner join `tabSales Invoice Item` `tabSales Invoice` inner join `tabSales Invoice Item`
on `tabSales Invoice Item`.parent = `tabSales Invoice`.name on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
{sales_team_table} {sales_team_table}
{payment_term_table}
where where
`tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening!='Yes' {conditions} {match_cond} `tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening!='Yes' {conditions} {match_cond}
order by order by
@ -655,6 +732,8 @@ class GrossProfitGenerator(object):
conditions=conditions, conditions=conditions,
sales_person_cols=sales_person_cols, sales_person_cols=sales_person_cols,
sales_team_table=sales_team_table, sales_team_table=sales_team_table,
payment_term_cols=payment_term_cols,
payment_term_table=payment_term_table,
match_cond=get_match_cond("Sales Invoice"), match_cond=get_match_cond("Sales Invoice"),
), ),
self.filters, self.filters,

View File

@ -367,8 +367,8 @@ def get_conditions(filters):
if not filters.get(field) or field in accounting_dimensions_list: if not filters.get(field) or field in accounting_dimensions_list:
return "" return ""
return f""" and exists(select name from `tab{table}` return f""" and exists(select name from `tab{table}`
where parent=`tabSales Invoice`.name where parent=`tabSales Invoice`.name
and ifnull(`tab{table}`.{field}, '') = %({field})s)""" and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
conditions += get_sales_invoice_item_field_condition("mode_of_payments", "Sales Invoice Payment") conditions += get_sales_invoice_item_field_condition("mode_of_payments", "Sales Invoice Payment")
conditions += get_sales_invoice_item_field_condition("cost_center") conditions += get_sales_invoice_item_field_condition("cost_center")

View File

@ -160,14 +160,12 @@ def get_rootwise_opening_balances(filters, report_type):
if filters.project: if filters.project:
additional_conditions += " and project = %(project)s" additional_conditions += " and project = %(project)s"
if filters.finance_book: if filters.get("include_default_book_entries"):
fb_conditions = " AND finance_book = %(finance_book)s" additional_conditions += (
if filters.include_default_book_entries: " AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
fb_conditions = ( )
" AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)" else:
) additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
additional_conditions += fb_conditions
accounting_dimensions = get_accounting_dimensions(as_list=False) accounting_dimensions = get_accounting_dimensions(as_list=False)

View File

@ -28,6 +28,7 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
("Item-wise Sales Register", {}), ("Item-wise Sales Register", {}),
("Item-wise Purchase Register", {}), ("Item-wise Purchase Register", {}),
("Sales Register", {}), ("Sales Register", {}),
("Sales Register", {"item_group": "All Item Groups"}),
("Purchase Register", {}), ("Purchase Register", {}),
( (
"Tax Detail", "Tax Detail",

View File

@ -1124,6 +1124,9 @@ def update_gl_entries_after(
def repost_gle_for_stock_vouchers( def repost_gle_for_stock_vouchers(
stock_vouchers, posting_date, company=None, warehouse_account=None stock_vouchers, posting_date, company=None, warehouse_account=None
): ):
from erpnext.accounts.general_ledger import toggle_debit_credit_if_negative
if not stock_vouchers: if not stock_vouchers:
return return
@ -1142,10 +1145,12 @@ def repost_gle_for_stock_vouchers(
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2
gle = get_voucherwise_gl_entries(stock_vouchers, posting_date) gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
for voucher_type, voucher_no in stock_vouchers: for idx, (voucher_type, voucher_no) in enumerate(stock_vouchers):
existing_gle = gle.get((voucher_type, voucher_no), []) existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no) voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account) # Some transactions post credit as negative debit, this is handled while posting GLE
# but while comparing we need to make sure it's flipped so comparisons are accurate
expected_gle = toggle_debit_credit_if_negative(voucher_obj.get_gl_entries(warehouse_account))
if expected_gle: if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle( if not existing_gle or not compare_existing_and_expected_gle(
existing_gle, expected_gle, precision existing_gle, expected_gle, precision
@ -1155,6 +1160,11 @@ def repost_gle_for_stock_vouchers(
else: else:
_delete_gl_entries(voucher_type, voucher_no) _delete_gl_entries(voucher_type, voucher_no)
if idx % 20 == 0:
# Commit every 20 documents to avoid losing progress
# and reducing memory usage
frappe.db.commit()
def sort_stock_vouchers_by_posting_date( def sort_stock_vouchers_by_posting_date(
stock_vouchers: List[Tuple[str, str]] stock_vouchers: List[Tuple[str, str]]

View File

@ -504,18 +504,6 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "DATEV Export",
"link_count": 0,
"link_to": "DATEV",
"link_type": "Report",
"onboard": 0,
"only_for": "Germany",
"type": "Link"
},
{ {
"dependencies": "GL Entry", "dependencies": "GL Entry",
"hidden": 0, "hidden": 0,
@ -1024,16 +1012,16 @@
"type": "Link" "type": "Link"
}, },
{ {
"dependencies": "Cost Center", "dependencies": "Cost Center",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Cost Center Allocation", "label": "Cost Center Allocation",
"link_count": 0, "link_count": 0,
"link_to": "Cost Center Allocation", "link_to": "Cost Center Allocation",
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{ {
"dependencies": "Cost Center", "dependencies": "Cost Center",
"hidden": 0, "hidden": 0,
@ -1235,13 +1223,14 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2022-01-13 17:25:09.835345", "modified": "2022-06-10 15:49:42.990860",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting", "name": "Accounting",
"owner": "Administrator", "owner": "Administrator",
"parent_page": "", "parent_page": "",
"public": 1, "public": 1,
"quick_lists": [],
"restrict_to_domain": "", "restrict_to_domain": "",
"roles": [], "roles": [],
"sequence_id": 2.0, "sequence_id": 2.0,

View File

@ -84,6 +84,9 @@ class Supplier(TransactionBase):
self.save() self.save()
def validate_internal_supplier(self): def validate_internal_supplier(self):
if not self.is_internal_supplier:
self.represents_company = ""
internal_supplier = frappe.db.get_value( internal_supplier = frappe.db.get_value(
"Supplier", "Supplier",
{ {

View File

@ -38,7 +38,7 @@ def is_search_module_loaded():
out = cache.execute_command("MODULE LIST") out = cache.execute_command("MODULE LIST")
parsed_output = " ".join( parsed_output = " ".join(
(" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out) (" ".join([frappe.as_unicode(s) for s in o if not isinstance(s, int)]) for o in out)
) )
return "search" in parsed_output return "search" in parsed_output
except Exception: except Exception:

View File

@ -1,44 +0,0 @@
import frappe
from frappe.data_migration.doctype.data_migration_connector.connectors.base import BaseConnection
from github import Github
class GithubConnection(BaseConnection):
def __init__(self, connector):
self.connector = connector
try:
password = self.get_password()
except frappe.AuthenticationError:
password = None
if self.connector.username and password:
self.connection = Github(self.connector.username, self.get_password())
else:
self.connection = Github()
self.name_field = 'id'
def insert(self, doctype, doc):
pass
def update(self, doctype, doc, migration_id):
pass
def delete(self, doctype, migration_id):
pass
def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10):
repo = filters.get('repo')
if remote_objectname == 'Milestone':
return self.get_milestones(repo, start, page_length)
if remote_objectname == 'Issue':
return self.get_issues(repo, start, page_length)
def get_milestones(self, repo, start=0, page_length=10):
_repo = self.connection.get_repo(repo)
return list(_repo.get_milestones()[start:start+page_length])
def get_issues(self, repo, start=0, page_length=10):
_repo = self.connection.get_repo(repo)
return list(_repo.get_issues()[start:start+page_length])

View File

@ -1,12 +0,0 @@
import frappe
def pre_process(issue):
project = frappe.db.get_value("Project", filters={"project_name": issue.milestone})
return {
"title": issue.title,
"body": frappe.utils.md_to_html(issue.body or ""),
"state": issue.state.title(),
"project": project or "",
}

View File

@ -1,36 +0,0 @@
{
"condition": "{\"repo\":\"frappe/erpnext\"}",
"creation": "2017-10-16 16:03:32.772191",
"docstatus": 0,
"doctype": "Data Migration Mapping",
"fields": [
{
"is_child_table": 0,
"local_fieldname": "subject",
"remote_fieldname": "title"
},
{
"is_child_table": 0,
"local_fieldname": "description",
"remote_fieldname": "body"
},
{
"is_child_table": 0,
"local_fieldname": "status",
"remote_fieldname": "state"
}
],
"idx": 0,
"local_doctype": "Task",
"local_primary_key": "name",
"mapping_name": "Issue to Task",
"mapping_type": "Pull",
"migration_id_field": "github_sync_id",
"modified": "2017-10-20 11:48:54.575993",
"modified_by": "Administrator",
"name": "Issue to Task",
"owner": "Administrator",
"page_length": 10,
"remote_objectname": "Issue",
"remote_primary_key": "id"
}

View File

@ -1,6 +0,0 @@
def pre_process(milestone):
return {
"title": milestone.title,
"description": milestone.description,
"state": milestone.state.title(),
}

View File

@ -1,36 +0,0 @@
{
"condition": "{\"repo\": \"frappe/erpnext\"}",
"creation": "2017-10-13 11:16:49.664925",
"docstatus": 0,
"doctype": "Data Migration Mapping",
"fields": [
{
"is_child_table": 0,
"local_fieldname": "project_name",
"remote_fieldname": "title"
},
{
"is_child_table": 0,
"local_fieldname": "notes",
"remote_fieldname": "description"
},
{
"is_child_table": 0,
"local_fieldname": "status",
"remote_fieldname": "state"
}
],
"idx": 0,
"local_doctype": "Project",
"local_primary_key": "project_name",
"mapping_name": "Milestone to Project",
"mapping_type": "Pull",
"migration_id_field": "github_sync_id",
"modified": "2017-10-20 11:48:54.552305",
"modified_by": "Administrator",
"name": "Milestone to Project",
"owner": "Administrator",
"page_length": 10,
"remote_objectname": "Milestone",
"remote_primary_key": "id"
}

View File

@ -1,22 +0,0 @@
{
"creation": "2017-10-13 11:16:53.600026",
"docstatus": 0,
"doctype": "Data Migration Plan",
"idx": 0,
"mappings": [
{
"enabled": 1,
"mapping": "Milestone to Project"
},
{
"enabled": 1,
"mapping": "Issue to Task"
}
],
"modified": "2017-10-20 11:48:54.496123",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "GitHub Sync",
"owner": "Administrator",
"plan_name": "GitHub Sync"
}

View File

@ -392,9 +392,12 @@ after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
scheduler_events = { scheduler_events = {
"cron": { "cron": {
"0/5 * * * *": [
"erpnext.manufacturing.doctype.bom_update_log.bom_update_log.resume_bom_cost_update_jobs",
],
"0/30 * * * *": [ "0/30 * * * *": [
"erpnext.utilities.doctype.video.video.update_youtube_data", "erpnext.utilities.doctype.video.video.update_youtube_data",
] ],
}, },
"all": [ "all": [
"erpnext.projects.doctype.project.project.project_status_update_reminder", "erpnext.projects.doctype.project.project.project_status_update_reminder",

View File

@ -827,7 +827,7 @@
"idx": 24, "idx": 24,
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"modified": "2022-04-22 16:21:55.811983", "modified": "2022-06-10 01:29:32.952091",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee", "name": "Employee",
@ -872,7 +872,6 @@
], ],
"search_fields": "employee_name", "search_fields": "employee_name",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"show_title_field_in_link": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],

View File

@ -55,6 +55,8 @@ def update_employee_work_history(employee, details, date=None, cancel=False):
new_data = getdate(new_data) new_data = getdate(new_data)
elif fieldtype == "Datetime" and new_data: elif fieldtype == "Datetime" and new_data:
new_data = get_datetime(new_data) new_data = get_datetime(new_data)
elif fieldtype in ["Currency", "Float"] and new_data:
new_data = flt(new_data)
setattr(employee, item.fieldname, new_data) setattr(employee, item.fieldname, new_data)
if item.fieldname in ["department", "designation", "branch"]: if item.fieldname in ["department", "designation", "branch"]:
internal_work_history[item.fieldname] = item.new internal_work_history[item.fieldname] = item.new

View File

@ -73,7 +73,7 @@ class Loan(AccountsController):
def on_cancel(self): def on_cancel(self):
self.unlink_loan_security_pledge() self.unlink_loan_security_pledge()
self.ignore_linked_doctypes = ["GL Entry"] self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
def set_missing_fields(self): def set_missing_fields(self):
if not self.company: if not self.company:

View File

@ -29,7 +29,7 @@ class LoanDisbursement(AccountsController):
def on_cancel(self): def on_cancel(self):
self.set_status_and_amounts(cancel=1) self.set_status_and_amounts(cancel=1)
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.ignore_linked_doctypes = ["GL Entry"] self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
def set_missing_values(self): def set_missing_values(self):
if not self.disbursement_date: if not self.disbursement_date:

View File

@ -32,7 +32,7 @@ class LoanInterestAccrual(AccountsController):
self.update_is_accrued() self.update_is_accrued()
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.ignore_linked_doctypes = ["GL Entry"] self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
def update_is_accrued(self): def update_is_accrued(self):
frappe.db.set_value("Repayment Schedule", self.repayment_schedule_name, "is_accrued", 0) frappe.db.set_value("Repayment Schedule", self.repayment_schedule_name, "is_accrued", 0)

View File

@ -41,7 +41,7 @@ class LoanRepayment(AccountsController):
self.check_future_accruals() self.check_future_accruals()
self.update_repayment_schedule(cancel=1) self.update_repayment_schedule(cancel=1)
self.mark_as_unpaid() self.mark_as_unpaid()
self.ignore_linked_doctypes = ["GL Entry"] self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
def set_missing_values(self, amounts): def set_missing_values(self, amounts):

View File

@ -42,7 +42,7 @@ class LoanWriteOff(AccountsController):
def on_cancel(self): def on_cancel(self):
self.update_outstanding_amount(cancel=1) self.update_outstanding_amount(cancel=1)
self.ignore_linked_doctypes = ["GL Entry"] self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
def update_outstanding_amount(self, cancel=0): def update_outstanding_amount(self, cancel=0):

View File

@ -81,7 +81,7 @@ frappe.ui.form.on("BOM", {
} }
) )
if (!frm.doc.__islocal && frm.doc.docstatus<2) { if (!frm.is_new() && frm.doc.docstatus<2) {
frm.add_custom_button(__("Update Cost"), function() { frm.add_custom_button(__("Update Cost"), function() {
frm.events.update_cost(frm, true); frm.events.update_cost(frm, true);
}); });
@ -93,10 +93,12 @@ frappe.ui.form.on("BOM", {
}); });
} }
frm.add_custom_button(__("New Version"), function() { if (!frm.is_new() && !frm.doc.docstatus == 0) {
let new_bom = frappe.model.copy_doc(frm.doc); frm.add_custom_button(__("New Version"), function() {
frappe.set_route("Form", "BOM", new_bom.name); let new_bom = frappe.model.copy_doc(frm.doc);
}); frappe.set_route("Form", "BOM", new_bom.name);
});
}
if(frm.doc.docstatus==1) { if(frm.doc.docstatus==1) {
frm.add_custom_button(__("Work Order"), function() { frm.add_custom_button(__("Work Order"), function() {

View File

@ -1,11 +1,11 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import functools import functools
import re import re
from collections import deque from collections import deque
from operator import itemgetter from operator import itemgetter
from typing import List from typing import Dict, List
import frappe import frappe
from frappe import _ from frappe import _
@ -189,6 +189,7 @@ class BOM(WebsiteGenerator):
self.validate_transfer_against() self.validate_transfer_against()
self.set_routing_operations() self.set_routing_operations()
self.validate_operations() self.validate_operations()
self.update_exploded_items(save=False)
self.calculate_cost() self.calculate_cost()
self.update_stock_qty() self.update_stock_qty()
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False) self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
@ -386,40 +387,14 @@ class BOM(WebsiteGenerator):
existing_bom_cost = self.total_cost existing_bom_cost = self.total_cost
for d in self.get("items"):
if not d.item_code:
continue
rate = self.get_rm_rate(
{
"company": self.company,
"item_code": d.item_code,
"bom_no": d.bom_no,
"qty": d.qty,
"uom": d.uom,
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor,
"sourced_by_supplier": d.sourced_by_supplier,
}
)
if rate:
d.rate = rate
d.amount = flt(d.rate) * flt(d.qty)
d.base_rate = flt(d.rate) * flt(self.conversion_rate)
d.base_amount = flt(d.amount) * flt(self.conversion_rate)
if save:
d.db_update()
if self.docstatus == 1: if self.docstatus == 1:
self.flags.ignore_validate_update_after_submit = True self.flags.ignore_validate_update_after_submit = True
self.calculate_cost(update_hour_rate)
self.calculate_cost(save_updates=save, update_hour_rate=update_hour_rate)
if save: if save:
self.db_update() self.db_update()
self.update_exploded_items(save=save)
# update parent BOMs # update parent BOMs
if self.total_cost != existing_bom_cost and update_parent: if self.total_cost != existing_bom_cost and update_parent:
parent_boms = frappe.db.sql_list( parent_boms = frappe.db.sql_list(
@ -608,11 +583,15 @@ class BOM(WebsiteGenerator):
bom_list.reverse() bom_list.reverse()
return bom_list return bom_list
def calculate_cost(self, update_hour_rate=False): def calculate_cost(self, save_updates=False, update_hour_rate=False):
"""Calculate bom totals""" """Calculate bom totals"""
self.calculate_op_cost(update_hour_rate) self.calculate_op_cost(update_hour_rate)
self.calculate_rm_cost() self.calculate_rm_cost(save=save_updates)
self.calculate_sm_cost() self.calculate_sm_cost(save=save_updates)
if save_updates:
# not via doc event, table is not regenerated and needs updation
self.calculate_exploded_cost()
self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost
self.base_total_cost = ( self.base_total_cost = (
self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
@ -654,12 +633,26 @@ class BOM(WebsiteGenerator):
if update_hour_rate: if update_hour_rate:
row.db_update() row.db_update()
def calculate_rm_cost(self): def calculate_rm_cost(self, save=False):
"""Fetch RM rate as per today's valuation rate and calculate totals""" """Fetch RM rate as per today's valuation rate and calculate totals"""
total_rm_cost = 0 total_rm_cost = 0
base_total_rm_cost = 0 base_total_rm_cost = 0
for d in self.get("items"): for d in self.get("items"):
old_rate = d.rate
d.rate = self.get_rm_rate(
{
"company": self.company,
"item_code": d.item_code,
"bom_no": d.bom_no,
"qty": d.qty,
"uom": d.uom,
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor,
"sourced_by_supplier": d.sourced_by_supplier,
}
)
d.base_rate = flt(d.rate) * flt(self.conversion_rate) d.base_rate = flt(d.rate) * flt(self.conversion_rate)
d.amount = flt(d.rate, d.precision("rate")) * flt(d.qty, d.precision("qty")) d.amount = flt(d.rate, d.precision("rate")) * flt(d.qty, d.precision("qty"))
d.base_amount = d.amount * flt(self.conversion_rate) d.base_amount = d.amount * flt(self.conversion_rate)
@ -669,11 +662,13 @@ class BOM(WebsiteGenerator):
total_rm_cost += d.amount total_rm_cost += d.amount
base_total_rm_cost += d.base_amount base_total_rm_cost += d.base_amount
if save and (old_rate != d.rate):
d.db_update()
self.raw_material_cost = total_rm_cost self.raw_material_cost = total_rm_cost
self.base_raw_material_cost = base_total_rm_cost self.base_raw_material_cost = base_total_rm_cost
def calculate_sm_cost(self): def calculate_sm_cost(self, save=False):
"""Fetch RM rate as per today's valuation rate and calculate totals""" """Fetch RM rate as per today's valuation rate and calculate totals"""
total_sm_cost = 0 total_sm_cost = 0
base_total_sm_cost = 0 base_total_sm_cost = 0
@ -688,10 +683,45 @@ class BOM(WebsiteGenerator):
) )
total_sm_cost += d.amount total_sm_cost += d.amount
base_total_sm_cost += d.base_amount base_total_sm_cost += d.base_amount
if save:
d.db_update()
self.scrap_material_cost = total_sm_cost self.scrap_material_cost = total_sm_cost
self.base_scrap_material_cost = base_total_sm_cost self.base_scrap_material_cost = base_total_sm_cost
def calculate_exploded_cost(self):
"Set exploded row cost from it's parent BOM."
rm_rate_map = self.get_rm_rate_map()
for row in self.get("exploded_items"):
old_rate = flt(row.rate)
row.rate = rm_rate_map.get(row.item_code)
row.amount = flt(row.stock_qty) * flt(row.rate)
if old_rate != row.rate:
# Only db_update if changed
row.db_update()
def get_rm_rate_map(self) -> Dict[str, float]:
"Create Raw Material-Rate map for Exploded Items. Fetch rate from Items table or Subassembly BOM."
rm_rate_map = {}
for item in self.get("items"):
if item.bom_no:
# Get Item-Rate from Subassembly BOM
explosion_items = frappe.get_all(
"BOM Explosion Item",
filters={"parent": item.bom_no},
fields=["item_code", "rate"],
order_by=None, # to avoid sort index creation at db level (granular change)
)
explosion_item_rate = {item.item_code: flt(item.rate) for item in explosion_items}
rm_rate_map.update(explosion_item_rate)
else:
rm_rate_map[item.item_code] = flt(item.base_rate) / flt(item.conversion_factor or 1.0)
return rm_rate_map
def update_exploded_items(self, save=True): def update_exploded_items(self, save=True):
"""Update Flat BOM, following will be correct data""" """Update Flat BOM, following will be correct data"""
self.get_exploded_items() self.get_exploded_items()
@ -902,44 +932,46 @@ def get_bom_item_rate(args, bom_doc):
return flt(rate) return flt(rate)
def get_valuation_rate(args): def get_valuation_rate(data):
"""Get weighted average of valuation rate from all warehouses""" """
1) Get average valuation rate from all warehouses
2) If no value, get last valuation rate from SLE
3) If no value, get valuation rate from Item
"""
from frappe.query_builder.functions import Sum
total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0 item_code, company = data.get("item_code"), data.get("company")
item_bins = frappe.db.sql( valuation_rate = 0.0
"""
select
bin.actual_qty, bin.stock_value
from
`tabBin` bin, `tabWarehouse` warehouse
where
bin.item_code=%(item)s
and bin.warehouse = warehouse.name
and warehouse.company=%(company)s""",
{"item": args["item_code"], "company": args["company"]},
as_dict=1,
)
for d in item_bins: bin_table = frappe.qb.DocType("Bin")
total_qty += flt(d.actual_qty) wh_table = frappe.qb.DocType("Warehouse")
total_value += flt(d.stock_value) item_valuation = (
frappe.qb.from_(bin_table)
.join(wh_table)
.on(bin_table.warehouse == wh_table.name)
.select((Sum(bin_table.stock_value) / Sum(bin_table.actual_qty)).as_("valuation_rate"))
.where((bin_table.item_code == item_code) & (wh_table.company == company))
).run(as_dict=True)[0]
if total_qty: valuation_rate = item_valuation.get("valuation_rate")
valuation_rate = total_value / total_qty
if valuation_rate <= 0: if (valuation_rate is not None) and valuation_rate <= 0:
last_valuation_rate = frappe.db.sql( # Explicit null value check. If None, Bins don't exist, neither does SLE
"""select valuation_rate sle = frappe.qb.DocType("Stock Ledger Entry")
from `tabStock Ledger Entry` last_val_rate = (
where item_code = %s and valuation_rate > 0 and is_cancelled = 0 frappe.qb.from_(sle)
order by posting_date desc, posting_time desc, creation desc limit 1""", .select(sle.valuation_rate)
args["item_code"], .where((sle.item_code == item_code) & (sle.valuation_rate > 0) & (sle.is_cancelled == 0))
) .orderby(sle.posting_date, order=frappe.qb.desc)
.orderby(sle.posting_time, order=frappe.qb.desc)
.orderby(sle.creation, order=frappe.qb.desc)
.limit(1)
).run(as_dict=True)
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 valuation_rate = flt(last_val_rate[0].get("valuation_rate")) if last_val_rate else 0
if not valuation_rate: if not valuation_rate:
valuation_rate = frappe.db.get_value("Item", args["item_code"], "valuation_rate") valuation_rate = frappe.db.get_value("Item", item_code, "valuation_rate")
return flt(valuation_rate) return flt(valuation_rate)
@ -1125,39 +1157,6 @@ def get_children(parent=None, is_root=False, **filters):
return bom_items return bom_items
def get_boms_in_bottom_up_order(bom_no=None):
def _get_parent(bom_no):
return frappe.db.sql_list(
"""
select distinct bom_item.parent from `tabBOM Item` bom_item
where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
""",
bom_no,
)
count = 0
bom_list = []
if bom_no:
bom_list.append(bom_no)
else:
# get all leaf BOMs
bom_list = frappe.db.sql_list(
"""select name from `tabBOM` bom
where docstatus=1 and is_active=1
and not exists(select bom_no from `tabBOM Item`
where parent=bom.name and ifnull(bom_no, '')!='')"""
)
while count < len(bom_list):
for child_bom in _get_parent(bom_list[count]):
if child_bom not in bom_list:
bom_list.append(child_bom)
count += 1
return bom_list
def add_additional_cost(stock_entry, work_order): def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost # Add non stock items cost in the additional cost
stock_entry.additional_costs = [] stock_entry.additional_costs = []

View File

@ -11,7 +11,9 @@ from frappe.utils import cstr, flt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query, make_variant_bom from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query, make_variant_bom
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import (
update_cost_in_all_boms_in_test,
)
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation, create_stock_reconciliation,
@ -69,26 +71,31 @@ class TestBOM(FrappeTestCase):
def test_update_bom_cost_in_all_boms(self): def test_update_bom_cost_in_all_boms(self):
# get current rate for '_Test Item 2' # get current rate for '_Test Item 2'
rm_rate = frappe.db.sql( bom_rates = frappe.db.get_values(
"""select rate from `tabBOM Item` "BOM Item",
where parent='BOM-_Test Item Home Desktop Manufactured-001' {
and item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""" "parent": "BOM-_Test Item Home Desktop Manufactured-001",
"item_code": "_Test Item 2",
"docstatus": 1,
},
fieldname=["rate", "base_rate"],
as_dict=True,
) )
rm_rate = rm_rate[0][0] if rm_rate else 0 rm_base_rate = bom_rates[0].get("base_rate") if bom_rates else 0
# Reset item valuation rate # Reset item valuation rate
reset_item_valuation_rate(item_code="_Test Item 2", qty=200, rate=rm_rate + 10) reset_item_valuation_rate(item_code="_Test Item 2", qty=200, rate=rm_base_rate + 10)
# update cost of all BOMs based on latest valuation rate # update cost of all BOMs based on latest valuation rate
update_cost() update_cost_in_all_boms_in_test()
# check if new valuation rate updated in all BOMs # check if new valuation rate updated in all BOMs
for d in frappe.db.sql( for d in frappe.db.sql(
"""select rate from `tabBOM Item` """select base_rate from `tabBOM Item`
where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""", where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""",
as_dict=1, as_dict=1,
): ):
self.assertEqual(d.rate, rm_rate + 10) self.assertEqual(d.base_rate, rm_base_rate + 10)
def test_bom_cost(self): def test_bom_cost(self):
bom = frappe.copy_doc(test_records[2]) bom = frappe.copy_doc(test_records[2])

View File

@ -32,6 +32,7 @@
"is_active": 1, "is_active": 1,
"is_default": 1, "is_default": 1,
"item": "_Test Item Home Desktop Manufactured", "item": "_Test Item Home Desktop Manufactured",
"company": "_Test Company",
"quantity": 1.0 "quantity": 1.0
}, },
{ {

View File

@ -169,13 +169,15 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-10-08 16:21:29.386212", "modified": "2022-05-27 13:42:23.305455",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Explosion Item", "name": "BOM Explosion Item",
"naming_rule": "Random",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@ -0,0 +1,55 @@
{
"actions": [],
"autoname": "autoincrement",
"creation": "2022-05-31 17:34:39.825537",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"level",
"batch_no",
"boms_updated",
"status"
],
"fields": [
{
"fieldname": "level",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Level"
},
{
"fieldname": "batch_no",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Batch No."
},
{
"fieldname": "boms_updated",
"fieldtype": "Long Text",
"hidden": 1,
"in_list_view": 1,
"label": "BOMs Updated"
},
{
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Pending\nCompleted",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-06-06 14:50:35.161062",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Update Batch",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class BOMUpdateBatch(Document):
pass

View File

@ -13,6 +13,10 @@
"update_type", "update_type",
"status", "status",
"error_log", "error_log",
"progress_section",
"current_level",
"processed_boms",
"bom_batches",
"amended_from" "amended_from"
], ],
"fields": [ "fields": [
@ -63,13 +67,36 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Error Log", "label": "Error Log",
"options": "Error Log" "options": "Error Log"
},
{
"collapsible": 1,
"depends_on": "eval: doc.update_type == \"Update Cost\"",
"fieldname": "progress_section",
"fieldtype": "Section Break",
"label": "Progress"
},
{
"fieldname": "processed_boms",
"fieldtype": "Long Text",
"hidden": 1,
"label": "Processed BOMs"
},
{
"fieldname": "bom_batches",
"fieldtype": "Table",
"options": "BOM Update Batch"
},
{
"fieldname": "current_level",
"fieldtype": "Int",
"label": "Current Level"
} }
], ],
"in_create": 1, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-03-31 12:51:44.885102", "modified": "2022-06-06 15:15:23.883251",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Update Log", "name": "BOM Update Log",

View File

@ -1,13 +1,20 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from typing import Dict, List, Literal, Optional import json
from typing import Any, Dict, List, Optional, Tuple, Union
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cstr, flt from frappe.utils import cint, cstr
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import (
get_leaf_boms,
get_next_higher_level_boms,
handle_exception,
replace_bom,
set_values_in_log,
)
class BOMMissingError(frappe.ValidationError): class BOMMissingError(frappe.ValidationError):
@ -20,6 +27,8 @@ class BOMUpdateLog(Document):
self.validate_boms_are_specified() self.validate_boms_are_specified()
self.validate_same_bom() self.validate_same_bom()
self.validate_bom_items() self.validate_bom_items()
else:
self.validate_bom_cost_update_in_progress()
self.status = "Queued" self.status = "Queued"
@ -42,123 +51,184 @@ class BOMUpdateLog(Document):
if current_bom_item != new_bom_item: if current_bom_item != new_bom_item:
frappe.throw(_("The selected BOMs are not for the same item")) frappe.throw(_("The selected BOMs are not for the same item"))
def on_submit(self): def validate_bom_cost_update_in_progress(self):
if frappe.flags.in_test: "If another Cost Updation Log is still in progress, dont make new ones."
return
wip_log = frappe.get_all(
"BOM Update Log",
{"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]},
limit_page_length=1,
)
if wip_log:
log_link = frappe.utils.get_link_to_form("BOM Update Log", wip_log[0].name)
frappe.throw(
_("BOM Updation already in progress. Please wait until {0} is complete.").format(log_link),
title=_("Note"),
)
def on_submit(self):
if self.update_type == "Replace BOM": if self.update_type == "Replace BOM":
boms = {"current_bom": self.current_bom, "new_bom": self.new_bom} boms = {"current_bom": self.current_bom, "new_bom": self.new_bom}
frappe.enqueue( frappe.enqueue(
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.run_bom_job", method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.run_replace_bom_job",
doc=self, doc=self,
boms=boms, boms=boms,
timeout=40000, timeout=40000,
now=frappe.flags.in_test,
) )
else: else:
frappe.enqueue( process_boms_cost_level_wise(self)
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.run_bom_job",
doc=self,
update_type="Update Cost",
timeout=40000,
)
def replace_bom(boms: Dict) -> None: def run_replace_bom_job(
"""Replace current BOM with new BOM in parent BOMs."""
current_bom = boms.get("current_bom")
new_bom = boms.get("new_bom")
unit_cost = get_new_bom_unit_cost(new_bom)
update_new_bom_in_bom_items(unit_cost, current_bom, new_bom)
frappe.cache().delete_key("bom_children")
parent_boms = get_parent_boms(new_bom)
for bom in parent_boms:
bom_obj = frappe.get_doc("BOM", bom)
# this is only used for versioning and we do not want
# to make separate db calls by using load_doc_before_save
# which proves to be expensive while doing bulk replace
bom_obj._doc_before_save = bom_obj
bom_obj.update_exploded_items()
bom_obj.calculate_cost()
bom_obj.update_parent_cost()
bom_obj.db_update()
if bom_obj.meta.get("track_changes") and not bom_obj.flags.ignore_version:
bom_obj.save_version()
def update_new_bom_in_bom_items(unit_cost: float, current_bom: str, new_bom: str) -> None:
bom_item = frappe.qb.DocType("BOM Item")
(
frappe.qb.update(bom_item)
.set(bom_item.bom_no, new_bom)
.set(bom_item.rate, unit_cost)
.set(bom_item.amount, (bom_item.stock_qty * unit_cost))
.where(
(bom_item.bom_no == current_bom) & (bom_item.docstatus < 2) & (bom_item.parenttype == "BOM")
)
).run()
def get_parent_boms(new_bom: str, bom_list: Optional[List] = None) -> List:
bom_list = bom_list or []
bom_item = frappe.qb.DocType("BOM Item")
parents = (
frappe.qb.from_(bom_item)
.select(bom_item.parent)
.where((bom_item.bom_no == new_bom) & (bom_item.docstatus < 2) & (bom_item.parenttype == "BOM"))
.run(as_dict=True)
)
for d in parents:
if new_bom == d.parent:
frappe.throw(_("BOM recursion: {0} cannot be child of {1}").format(new_bom, d.parent))
bom_list.append(d.parent)
get_parent_boms(d.parent, bom_list)
return list(set(bom_list))
def get_new_bom_unit_cost(new_bom: str) -> float:
bom = frappe.qb.DocType("BOM")
new_bom_unitcost = (
frappe.qb.from_(bom).select(bom.total_cost / bom.quantity).where(bom.name == new_bom).run()
)
return flt(new_bom_unitcost[0][0])
def run_bom_job(
doc: "BOMUpdateLog", doc: "BOMUpdateLog",
boms: Optional[Dict[str, str]] = None, boms: Optional[Dict[str, str]] = None,
update_type: Literal["Replace BOM", "Update Cost"] = "Replace BOM",
) -> None: ) -> None:
try: try:
doc.db_set("status", "In Progress") doc.db_set("status", "In Progress")
if not frappe.flags.in_test: if not frappe.flags.in_test:
frappe.db.commit() frappe.db.commit()
frappe.db.auto_commit_on_many_writes = 1 frappe.db.auto_commit_on_many_writes = 1
boms = frappe._dict(boms or {}) boms = frappe._dict(boms or {})
replace_bom(boms, doc.name)
if update_type == "Replace BOM":
replace_bom(boms)
else:
update_cost()
doc.db_set("status", "Completed") doc.db_set("status", "Completed")
except Exception: except Exception:
frappe.db.rollback() handle_exception(doc)
error_log = doc.log_error("BOM Update Tool Error")
doc.db_set("status", "Failed")
doc.db_set("error_log", error_log.name)
finally: finally:
frappe.db.auto_commit_on_many_writes = 0 frappe.db.auto_commit_on_many_writes = 0
frappe.db.commit() # nosemgrep
if not frappe.flags.in_test:
frappe.db.commit() # nosemgrep
def process_boms_cost_level_wise(
update_doc: "BOMUpdateLog", parent_boms: List[str] = None
) -> Union[None, Tuple]:
"Queue jobs at the start of new BOM Level in 'Update Cost' Jobs."
current_boms = {}
values = {}
if update_doc.status == "Queued":
# First level yet to process. On Submit.
current_level = 0
current_boms = get_leaf_boms()
values = {
"processed_boms": json.dumps({}),
"status": "In Progress",
"current_level": current_level,
}
else:
# Resume next level. via Cron Job.
if not parent_boms:
return
current_level = cint(update_doc.current_level) + 1
# Process the next level BOMs. Stage parents as current BOMs.
current_boms = parent_boms.copy()
values = {"current_level": current_level}
set_values_in_log(update_doc.name, values, commit=True)
queue_bom_cost_jobs(current_boms, update_doc, current_level)
def queue_bom_cost_jobs(
current_boms_list: List[str], update_doc: "BOMUpdateLog", current_level: int
) -> None:
"Queue batches of 20k BOMs of the same level to process parallelly"
batch_no = 0
while current_boms_list:
batch_no += 1
batch_size = 20_000
boms_to_process = current_boms_list[:batch_size] # slice out batch of 20k BOMs
# update list to exclude 20K (queued) BOMs
current_boms_list = current_boms_list[batch_size:] if len(current_boms_list) > batch_size else []
batch_row = update_doc.append(
"bom_batches", {"level": current_level, "batch_no": batch_no, "status": "Pending"}
)
batch_row.db_insert()
frappe.enqueue(
method="erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils.update_cost_in_level",
doc=update_doc,
bom_list=boms_to_process,
batch_name=batch_row.name,
queue="long",
now=frappe.flags.in_test,
)
def resume_bom_cost_update_jobs():
"""
1. Checks for In Progress BOM Update Log.
2. Checks if this job has completed the _current level_.
3. If current level is complete, get parent BOMs and start next level.
4. If no parents, mark as Complete.
5. If current level is WIP, skip the Log.
Called every 5 minutes via Cron job.
"""
in_progress_logs = frappe.db.get_all(
"BOM Update Log",
{"update_type": "Update Cost", "status": "In Progress"},
["name", "processed_boms", "current_level"],
)
if not in_progress_logs:
return
for log in in_progress_logs:
# check if all log batches of current level are processed
bom_batches = frappe.db.get_all(
"BOM Update Batch",
{"parent": log.name, "level": log.current_level},
["name", "boms_updated", "status"],
)
incomplete_level = any(row.get("status") == "Pending" for row in bom_batches)
if not bom_batches or incomplete_level:
continue
# Prep parent BOMs & updated processed BOMs for next level
current_boms, processed_boms = get_processed_current_boms(log, bom_batches)
parent_boms = get_next_higher_level_boms(child_boms=current_boms, processed_boms=processed_boms)
# Unset processed BOMs if log is complete, it is used for next level BOMs
set_values_in_log(
log.name,
values={
"processed_boms": json.dumps([] if not parent_boms else processed_boms),
"status": "Completed" if not parent_boms else "In Progress",
},
commit=True,
)
if parent_boms: # there is a next level to process
process_boms_cost_level_wise(
update_doc=frappe.get_doc("BOM Update Log", log.name), parent_boms=parent_boms
)
def get_processed_current_boms(
log: Dict[str, Any], bom_batches: Dict[str, Any]
) -> Tuple[List[str], Dict[str, Any]]:
"""
Aggregate all BOMs from BOM Update Batch rows into 'processed_boms' field
and into current boms list.
"""
processed_boms = json.loads(log.processed_boms) if log.processed_boms else {}
current_boms = []
for row in bom_batches:
boms_updated = json.loads(row.boms_updated)
current_boms.extend(boms_updated)
boms_updated_dict = {bom: True for bom in boms_updated}
processed_boms.update(boms_updated_dict)
return current_boms, processed_boms

View File

@ -0,0 +1,225 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import copy
import json
from collections import defaultdict
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
if TYPE_CHECKING:
from erpnext.manufacturing.doctype.bom_update_log.bom_update_log import BOMUpdateLog
import frappe
from frappe import _
def replace_bom(boms: Dict, log_name: str) -> None:
"Replace current BOM with new BOM in parent BOMs."
current_bom = boms.get("current_bom")
new_bom = boms.get("new_bom")
unit_cost = get_bom_unit_cost(new_bom)
update_new_bom_in_bom_items(unit_cost, current_bom, new_bom)
frappe.cache().delete_key("bom_children")
parent_boms = get_ancestor_boms(new_bom)
for bom in parent_boms:
bom_obj = frappe.get_doc("BOM", bom)
# this is only used for versioning and we do not want
# to make separate db calls by using load_doc_before_save
# which proves to be expensive while doing bulk replace
bom_obj._doc_before_save = copy.deepcopy(bom_obj)
bom_obj.update_exploded_items()
bom_obj.calculate_cost()
bom_obj.update_parent_cost()
bom_obj.db_update()
bom_obj.flags.updater_reference = {
"doctype": "BOM Update Log",
"docname": log_name,
"label": _("via BOM Update Tool"),
}
bom_obj.save_version()
def update_cost_in_level(
doc: "BOMUpdateLog", bom_list: List[str], batch_name: Union[int, str]
) -> None:
"Updates Cost for BOMs within a given level. Runs via background jobs."
try:
status = frappe.db.get_value("BOM Update Log", doc.name, "status")
if status == "Failed":
return
update_cost_in_boms(bom_list=bom_list) # main updation logic
bom_batch = frappe.qb.DocType("BOM Update Batch")
(
frappe.qb.update(bom_batch)
.set(bom_batch.boms_updated, json.dumps(bom_list))
.set(bom_batch.status, "Completed")
.where(bom_batch.name == batch_name)
).run()
except Exception:
handle_exception(doc)
finally:
if not frappe.flags.in_test:
frappe.db.commit() # nosemgrep
def get_ancestor_boms(new_bom: str, bom_list: Optional[List] = None) -> List:
"Recursively get all ancestors of BOM."
bom_list = bom_list or []
bom_item = frappe.qb.DocType("BOM Item")
parents = (
frappe.qb.from_(bom_item)
.select(bom_item.parent)
.where((bom_item.bom_no == new_bom) & (bom_item.docstatus < 2) & (bom_item.parenttype == "BOM"))
.run(as_dict=True)
)
for d in parents:
if new_bom == d.parent:
frappe.throw(_("BOM recursion: {0} cannot be child of {1}").format(new_bom, d.parent))
bom_list.append(d.parent)
get_ancestor_boms(d.parent, bom_list)
return list(set(bom_list))
def update_new_bom_in_bom_items(unit_cost: float, current_bom: str, new_bom: str) -> None:
bom_item = frappe.qb.DocType("BOM Item")
(
frappe.qb.update(bom_item)
.set(bom_item.bom_no, new_bom)
.set(bom_item.rate, unit_cost)
.set(bom_item.amount, (bom_item.stock_qty * unit_cost))
.where(
(bom_item.bom_no == current_bom) & (bom_item.docstatus < 2) & (bom_item.parenttype == "BOM")
)
).run()
def get_bom_unit_cost(bom_name: str) -> float:
bom = frappe.qb.DocType("BOM")
new_bom_unitcost = (
frappe.qb.from_(bom).select(bom.total_cost / bom.quantity).where(bom.name == bom_name).run()
)
return frappe.utils.flt(new_bom_unitcost[0][0])
def update_cost_in_boms(bom_list: List[str]) -> None:
"Updates cost in given BOMs. Returns current and total updated BOMs."
for index, bom in enumerate(bom_list):
bom_doc = frappe.get_doc("BOM", bom, for_update=True)
bom_doc.calculate_cost(save_updates=True, update_hour_rate=True)
bom_doc.db_update()
if (index % 50 == 0) and not frappe.flags.in_test:
frappe.db.commit() # nosemgrep
def get_next_higher_level_boms(
child_boms: List[str], processed_boms: Dict[str, bool]
) -> List[str]:
"Generate immediate higher level dependants with no unresolved dependencies (children)."
def _all_children_are_processed(parent_bom):
child_boms = dependency_map.get(parent_bom)
return all(processed_boms.get(bom) for bom in child_boms)
dependants_map, dependency_map = _generate_dependence_map()
dependants = []
for bom in child_boms:
# generate list of immediate dependants
parents = dependants_map.get(bom) or []
dependants.extend(parents)
dependants = set(dependants) # remove duplicates
resolved_dependants = set()
# consider only if children are all resolved
for parent_bom in dependants:
if _all_children_are_processed(parent_bom):
resolved_dependants.add(parent_bom)
return list(resolved_dependants)
def get_leaf_boms() -> List[str]:
"Get BOMs that have no dependencies."
return frappe.db.sql_list(
"""select name from `tabBOM` bom
where docstatus=1 and is_active=1
and not exists(select bom_no from `tabBOM Item`
where parent=bom.name and ifnull(bom_no, '')!='')"""
)
def _generate_dependence_map() -> defaultdict:
"""
Generate maps such as: { BOM-1: [Dependant-BOM-1, Dependant-BOM-2, ..] }.
Here BOM-1 is the leaf/lower level node/dependency.
The list contains one level higher nodes/dependants that depend on BOM-1.
Generate and return the reverse as well.
"""
bom = frappe.qb.DocType("BOM")
bom_item = frappe.qb.DocType("BOM Item")
bom_items = (
frappe.qb.from_(bom_item)
.join(bom)
.on(bom_item.parent == bom.name)
.select(bom_item.bom_no, bom_item.parent)
.where(
(bom_item.bom_no.isnotnull())
& (bom_item.bom_no != "")
& (bom.docstatus == 1)
& (bom.is_active == 1)
& (bom_item.parenttype == "BOM")
)
).run(as_dict=True)
child_parent_map = defaultdict(list)
parent_child_map = defaultdict(list)
for row in bom_items:
child_parent_map[row.bom_no].append(row.parent)
parent_child_map[row.parent].append(row.bom_no)
return child_parent_map, parent_child_map
def set_values_in_log(log_name: str, values: Dict[str, Any], commit: bool = False) -> None:
"Update BOM Update Log record."
if not values:
return
bom_update_log = frappe.qb.DocType("BOM Update Log")
query = frappe.qb.update(bom_update_log).where(bom_update_log.name == log_name)
for key, value in values.items():
query = query.set(key, value)
query.run()
if commit and not frappe.flags.in_test:
frappe.db.commit() # nosemgrep
def handle_exception(doc: "BOMUpdateLog") -> None:
"Rolls back and fails BOM Update Log."
frappe.db.rollback()
error_log = doc.log_error("BOM Update Tool Error")
set_values_in_log(doc.name, {"status": "Failed", "error_log": error_log.name})

View File

@ -6,9 +6,12 @@ from frappe.tests.utils import FrappeTestCase
from erpnext.manufacturing.doctype.bom_update_log.bom_update_log import ( from erpnext.manufacturing.doctype.bom_update_log.bom_update_log import (
BOMMissingError, BOMMissingError,
run_bom_job, resume_bom_cost_update_jobs,
)
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import (
enqueue_replace_bom,
enqueue_update_cost,
) )
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import enqueue_replace_bom
test_records = frappe.get_test_records("BOM") test_records = frappe.get_test_records("BOM")
@ -31,17 +34,12 @@ class TestBOMUpdateLog(FrappeTestCase):
def tearDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
if self._testMethodName == "test_bom_update_log_completion":
# clear logs and delete BOM created via setUp
frappe.db.delete("BOM Update Log")
self.new_bom_doc.cancel()
self.new_bom_doc.delete()
# explicitly commit and restore to original state
frappe.db.commit() # nosemgrep
def test_bom_update_log_validate(self): def test_bom_update_log_validate(self):
"Test if BOM presence is validated." """
1) Test if BOM presence is validated.
2) Test if same BOMs are validated.
3) Test of non-existent BOM is validated.
"""
with self.assertRaises(BOMMissingError): with self.assertRaises(BOMMissingError):
enqueue_replace_bom(boms={}) enqueue_replace_bom(boms={})
@ -52,45 +50,22 @@ class TestBOMUpdateLog(FrappeTestCase):
with self.assertRaises(frappe.ValidationError): with self.assertRaises(frappe.ValidationError):
enqueue_replace_bom(boms=frappe._dict(current_bom=self.boms.new_bom, new_bom="Dummy BOM")) enqueue_replace_bom(boms=frappe._dict(current_bom=self.boms.new_bom, new_bom="Dummy BOM"))
def test_bom_update_log_queueing(self):
"Test if BOM Update Log is created and queued."
log = enqueue_replace_bom(
boms=self.boms,
)
self.assertEqual(log.docstatus, 1)
self.assertEqual(log.status, "Queued")
def test_bom_update_log_completion(self): def test_bom_update_log_completion(self):
"Test if BOM Update Log handles job completion correctly." "Test if BOM Update Log handles job completion correctly."
log = enqueue_replace_bom( log = enqueue_replace_bom(boms=self.boms)
boms=self.boms,
)
# Explicitly commits log, new bom (setUp) and replacement impact.
# Is run via background jobs IRL
run_bom_job(
doc=log,
boms=self.boms,
update_type="Replace BOM",
)
log.reload() log.reload()
self.assertEqual(log.status, "Completed") self.assertEqual(log.status, "Completed")
# teardown (undo replace impact) due to commit
boms = frappe._dict( def update_cost_in_all_boms_in_test():
current_bom=self.boms.new_bom, """
new_bom=self.boms.current_bom, Utility to run 'Update Cost' job in tests without Cron job until fully complete.
) """
log2 = enqueue_replace_bom( log = enqueue_update_cost() # create BOM Update Log
boms=self.boms,
) while log.status != "Completed":
run_bom_job( # Explicitly commits resume_bom_cost_update_jobs() # run cron job until complete
doc=log2, log.reload()
boms=boms,
update_type="Replace BOM", return log
)
self.assertEqual(log2.status, "Completed")

View File

@ -10,8 +10,6 @@ if TYPE_CHECKING:
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
class BOMUpdateTool(Document): class BOMUpdateTool(Document):
pass pass
@ -40,14 +38,13 @@ def enqueue_update_cost() -> "BOMUpdateLog":
def auto_update_latest_price_in_all_boms() -> None: def auto_update_latest_price_in_all_boms() -> None:
"""Called via hooks.py.""" """Called via hooks.py."""
if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"): if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"):
update_cost() wip_log = frappe.get_all(
"BOM Update Log",
{"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]},
def update_cost() -> None: limit_page_length=1,
"""Updates Cost for all BOMs from bottom to top.""" )
bom_list = get_boms_in_bottom_up_order() if not wip_log:
for bom in bom_list: create_bom_update_log(update_type="Update Cost")
frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True)
def create_bom_update_log( def create_bom_update_log(

View File

@ -1,11 +1,13 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from erpnext.manufacturing.doctype.bom_update_log.bom_update_log import replace_bom from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import (
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost update_cost_in_all_boms_in_test,
)
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import enqueue_replace_bom
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
@ -15,6 +17,9 @@ test_records = frappe.get_test_records("BOM")
class TestBOMUpdateTool(FrappeTestCase): class TestBOMUpdateTool(FrappeTestCase):
"Test major functions run via BOM Update Tool." "Test major functions run via BOM Update Tool."
def tearDown(self):
frappe.db.rollback()
def test_replace_bom(self): def test_replace_bom(self):
current_bom = "BOM-_Test Item Home Desktop Manufactured-001" current_bom = "BOM-_Test Item Home Desktop Manufactured-001"
@ -23,15 +28,10 @@ class TestBOMUpdateTool(FrappeTestCase):
bom_doc.insert() bom_doc.insert()
boms = frappe._dict(current_bom=current_bom, new_bom=bom_doc.name) boms = frappe._dict(current_bom=current_bom, new_bom=bom_doc.name)
replace_bom(boms) enqueue_replace_bom(boms=boms)
self.assertFalse(frappe.db.sql("select name from `tabBOM Item` where bom_no=%s", current_bom)) self.assertFalse(frappe.db.exists("BOM Item", {"bom_no": current_bom, "docstatus": 1}))
self.assertTrue(frappe.db.sql("select name from `tabBOM Item` where bom_no=%s", bom_doc.name)) self.assertTrue(frappe.db.exists("BOM Item", {"bom_no": bom_doc.name, "docstatus": 1}))
# reverse, as it affects other testcases
boms.current_bom = bom_doc.name
boms.new_bom = current_bom
replace_bom(boms)
def test_bom_cost(self): def test_bom_cost(self):
for item in ["BOM Cost Test Item 1", "BOM Cost Test Item 2", "BOM Cost Test Item 3"]: for item in ["BOM Cost Test Item 1", "BOM Cost Test Item 2", "BOM Cost Test Item 3"]:
@ -52,13 +52,13 @@ class TestBOMUpdateTool(FrappeTestCase):
self.assertEqual(doc.total_cost, 200) self.assertEqual(doc.total_cost, 200)
frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 200) frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 200)
update_cost() update_cost_in_all_boms_in_test()
doc.load_from_db() doc.load_from_db()
self.assertEqual(doc.total_cost, 300) self.assertEqual(doc.total_cost, 300)
frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 100) frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 100)
update_cost() update_cost_in_all_boms_in_test()
doc.load_from_db() doc.load_from_db()
self.assertEqual(doc.total_cost, 200) self.assertEqual(doc.total_cost, 200)

View File

@ -626,14 +626,15 @@ class JobCard(Document):
self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0] self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
if self.for_quantity <= self.transferred_qty: if self.docstatus < 2:
self.status = "Material Transferred" if self.for_quantity <= self.transferred_qty:
self.status = "Material Transferred"
if self.time_logs: if self.time_logs:
self.status = "Work In Progress" self.status = "Work In Progress"
if self.docstatus == 1 and (self.for_quantity <= self.total_completed_qty or not self.items): if self.docstatus == 1 and (self.for_quantity <= self.total_completed_qty or not self.items):
self.status = "Completed" self.status = "Completed"
if update_status: if update_status:
self.db_set("status", self.status) self.db_set("status", self.status)

View File

@ -1,16 +1,17 @@
frappe.listview_settings['Job Card'] = { frappe.listview_settings['Job Card'] = {
has_indicator_for_draft: true, has_indicator_for_draft: true,
get_indicator: function(doc) { get_indicator: function(doc) {
if (doc.status === "Work In Progress") { const status_colors = {
return [__("Work In Progress"), "orange", "status,=,Work In Progress"]; "Work In Progress": "orange",
} else if (doc.status === "Completed") { "Completed": "green",
return [__("Completed"), "green", "status,=,Completed"]; "Cancelled": "red",
} else if (doc.docstatus == 2) { "Material Transferred": "blue",
return [__("Cancelled"), "red", "status,=,Cancelled"]; "Open": "red",
} else if (doc.status === "Material Transferred") { };
return [__('Material Transferred'), "blue", "status,=,Material Transferred"]; const status = doc.status || "Open";
} else { const color = status_colors[status] || "blue";
return [__("Open"), "red", "status,=,Open"];
} return [__(status), color, `status,=,${status}`];
} }
}; };

View File

@ -344,6 +344,30 @@ class TestJobCard(FrappeTestCase):
cost_after_cancel = self.work_order.total_operating_cost cost_after_cancel = self.work_order.total_operating_cost
self.assertEqual(cost_after_cancel, original_cost) self.assertEqual(cost_after_cancel, original_cost)
def test_job_card_statuses(self):
def assertStatus(status):
jc.set_status()
self.assertEqual(jc.status, status)
jc = frappe.new_doc("Job Card")
jc.for_quantity = 2
jc.transferred_qty = 1
jc.total_completed_qty = 0
assertStatus("Open")
jc.transferred_qty = jc.for_quantity
assertStatus("Material Transferred")
jc.append("time_logs", {})
assertStatus("Work In Progress")
jc.docstatus = 1
jc.total_completed_qty = jc.for_quantity
assertStatus("Completed")
jc.docstatus = 2
assertStatus("Cancelled")
def create_bom_with_multiple_operations(): def create_bom_with_multiple_operations():
"Create a BOM with multiple operations and Material Transfer against Job Card" "Create a BOM with multiple operations and Material Transfer against Job Card"

View File

@ -798,7 +798,6 @@ def make_bom(**args):
for item in args.raw_materials: for item in args.raw_materials:
item_doc = frappe.get_doc("Item", item) item_doc = frappe.get_doc("Item", item)
bom.append( bom.append(
"items", "items",
{ {

View File

@ -417,7 +417,7 @@ class TestWorkOrder(FrappeTestCase):
"doctype": "Item Price", "doctype": "Item Price",
"item_code": "_Test FG Non Stock Item", "item_code": "_Test FG Non Stock Item",
"price_list_rate": 1000, "price_list_rate": 1000,
"price_list": "Standard Buying", "price_list": "_Test Price List India",
} }
).insert(ignore_permissions=True) ).insert(ignore_permissions=True)
@ -426,8 +426,17 @@ class TestWorkOrder(FrappeTestCase):
item_code="_Test FG Item", target="_Test Warehouse - _TC", qty=1, basic_rate=100 item_code="_Test FG Item", target="_Test Warehouse - _TC", qty=1, basic_rate=100
) )
if not frappe.db.get_value("BOM", {"item": fg_item}): if not frappe.db.get_value("BOM", {"item": fg_item, "docstatus": 1}):
make_bom(item=fg_item, rate=1000, raw_materials=["_Test FG Item", "_Test FG Non Stock Item"]) bom = make_bom(
item=fg_item,
rate=1000,
raw_materials=["_Test FG Item", "_Test FG Non Stock Item"],
do_not_save=True,
)
bom.rm_cost_as_per = "Price List" # non stock item won't have valuation rate
bom.buying_price_list = "_Test Price List India"
bom.currency = "INR"
bom.save()
wo = make_wo_order_test_record(production_item=fg_item) wo = make_wo_order_test_record(production_item=fg_item)

View File

@ -1,11 +1,13 @@
import frappe import frappe
from frappe import qb from frappe import qb
from frappe.query_builder import Case
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import IfNull
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_dimensions, get_dimensions,
make_dimension_in_accounting_doctypes, make_dimension_in_accounting_doctypes,
) )
from erpnext.accounts.utils import create_payment_ledger_entry
def create_accounting_dimension_fields(): def create_accounting_dimension_fields():
@ -15,24 +17,119 @@ def create_accounting_dimension_fields():
make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"]) make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"])
def execute(): def generate_name_for_payment_ledger_entries(gl_entries):
# create accounting dimension fields in Payment Ledger for index, entry in enumerate(gl_entries, 1):
create_accounting_dimension_fields() entry.name = index
def get_columns():
columns = [
"name",
"creation",
"modified",
"modified_by",
"owner",
"docstatus",
"posting_date",
"account_type",
"account",
"party_type",
"party",
"voucher_type",
"voucher_no",
"against_voucher_type",
"against_voucher_no",
"amount",
"amount_in_account_currency",
"account_currency",
"company",
"cost_center",
"due_date",
"finance_book",
]
dimensions_and_defaults = get_dimensions()
if dimensions_and_defaults:
for dimension in dimensions_and_defaults[0]:
columns.append(dimension.fieldname)
return columns
def build_insert_query():
ple = qb.DocType("Payment Ledger Entry")
columns = get_columns()
insert_query = qb.into(ple)
# build 'insert' columns in query
insert_query = insert_query.columns(tuple(columns))
return insert_query
def insert_chunk_into_payment_ledger(insert_query, gl_entries):
if gl_entries:
columns = get_columns()
# build tuple of data with same column order
for entry in gl_entries:
data = ()
for column in columns:
data += (entry[column],)
insert_query = insert_query.insert(data)
insert_query.run()
def execute():
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
# create accounting dimension fields in Payment Ledger
create_accounting_dimension_fields()
gl = qb.DocType("GL Entry")
account = qb.DocType("Account")
gl = qb.DocType("GL Entry")
accounts = frappe.db.get_list(
"Account", "name", filters={"account_type": ["in", ["Receivable", "Payable"]]}, as_list=True
)
gl_entries = []
if accounts:
# get all gl entries on receivable/payable accounts
gl_entries = ( gl_entries = (
qb.from_(gl) qb.from_(gl)
.select("*") .inner_join(account)
.where(gl.account.isin(accounts)) .on((gl.account == account.name) & (account.account_type.isin(["Receivable", "Payable"])))
.select(
gl.star,
ConstantColumn(1).as_("docstatus"),
account.account_type.as_("account_type"),
IfNull(gl.against_voucher_type, gl.voucher_type).as_("against_voucher_type"),
IfNull(gl.against_voucher, gl.voucher_no).as_("against_voucher_no"),
# convert debit/credit to amount
Case()
.when(account.account_type == "Receivable", gl.debit - gl.credit)
.else_(gl.credit - gl.debit)
.as_("amount"),
# convert debit/credit in account currency to amount in account currency
Case()
.when(
account.account_type == "Receivable",
gl.debit_in_account_currency - gl.credit_in_account_currency,
)
.else_(gl.credit_in_account_currency - gl.debit_in_account_currency)
.as_("amount_in_account_currency"),
)
.where(gl.is_cancelled == 0) .where(gl.is_cancelled == 0)
.orderby(gl.creation)
.run(as_dict=True) .run(as_dict=True)
) )
if gl_entries:
# create payment ledger entries for the accounts receivable/payable # primary key(name) for payment ledger records
create_payment_ledger_entry(gl_entries, 0) generate_name_for_payment_ledger_entries(gl_entries)
# split data into chunks
chunk_size = 1000
try:
for i in range(0, len(gl_entries), chunk_size):
insert_query = build_insert_query()
insert_chunk_into_payment_ledger(insert_query, gl_entries[i : i + chunk_size])
frappe.db.commit()
except Exception as err:
frappe.db.rollback()
ple = qb.DocType("Payment Ledger Entry")
qb.from_(ple).delete().where(ple.docstatus >= 0).run()
frappe.db.commit()
raise err

View File

@ -244,11 +244,10 @@ class GSTR3BReport(Document):
) )
for d in item_details: for d in item_details:
if d.item_code not in self.invoice_items.get(d.parent, {}): self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) self.invoice_items[d.parent][d.item_code] += d.get("taxable_value", 0) or d.get(
self.invoice_items[d.parent][d.item_code] += d.get("taxable_value", 0) or d.get( "base_net_amount", 0
"base_net_amount", 0 )
)
if d.is_nil_exempt and d.item_code not in self.is_nil_exempt: if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
self.is_nil_exempt.append(d.item_code) self.is_nil_exempt.append(d.item_code)
@ -335,7 +334,6 @@ class GSTR3BReport(Document):
def set_outward_taxable_supplies(self): def set_outward_taxable_supplies(self):
inter_state_supply_details = {} inter_state_supply_details = {}
for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
gst_category = self.invoice_detail_map.get(inv, {}).get("gst_category") gst_category = self.invoice_detail_map.get(inv, {}).get("gst_category")
place_of_supply = ( place_of_supply = (
@ -361,7 +359,6 @@ class GSTR3BReport(Document):
else: else:
self.report_dict["sup_details"]["osup_det"]["iamt"] += taxable_value * rate / 100 self.report_dict["sup_details"]["osup_det"]["iamt"] += taxable_value * rate / 100
self.report_dict["sup_details"]["osup_det"]["txval"] += taxable_value self.report_dict["sup_details"]["osup_det"]["txval"] += taxable_value
if ( if (
gst_category in ["Unregistered", "Registered Composition", "UIN Holders"] gst_category in ["Unregistered", "Registered Composition", "UIN Holders"]
and self.gst_details.get("gst_state") != place_of_supply.split("-")[1] and self.gst_details.get("gst_state") != place_of_supply.split("-")[1]

View File

@ -55,6 +55,9 @@ def validate_eligibility(doc):
return False return False
invalid_company = not frappe.db.get_value("E Invoice User", {"company": doc.get("company")}) invalid_company = not frappe.db.get_value("E Invoice User", {"company": doc.get("company")})
invalid_company_gstin = not frappe.db.get_value(
"E Invoice User", {"gstin": doc.get("company_gstin")}
)
invalid_supply_type = doc.get("gst_category") not in [ invalid_supply_type = doc.get("gst_category") not in [
"Registered Regular", "Registered Regular",
"Registered Composition", "Registered Composition",
@ -71,6 +74,7 @@ def validate_eligibility(doc):
if ( if (
invalid_company invalid_company
or invalid_company_gstin
or invalid_supply_type or invalid_supply_type
or company_transaction or company_transaction
or no_taxes_applied or no_taxes_applied

View File

@ -141,6 +141,9 @@ class Customer(TransactionBase):
) )
def validate_internal_customer(self): def validate_internal_customer(self):
if not self.is_internal_customer:
self.represents_company = ""
internal_customer = frappe.db.get_value( internal_customer = frappe.db.get_value(
"Customer", "Customer",
{ {

View File

@ -1359,6 +1359,8 @@
"width": "50%" "width": "50%"
}, },
{ {
"fetch_from": "sales_partner.commission_rate",
"fetch_if_empty": 1,
"fieldname": "commission_rate", "fieldname": "commission_rate",
"fieldtype": "Float", "fieldtype": "Float",
"hide_days": 1, "hide_days": 1,
@ -1547,7 +1549,7 @@
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-04-26 14:38:18.350207", "modified": "2022-06-10 03:52:22.212953",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order", "name": "Sales Order",

View File

@ -12,8 +12,6 @@ frappe.provide("erpnext.selling");
erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController { erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController {
setup() { setup() {
super.setup(); super.setup();
this.frm.add_fetch("sales_partner", "commission_rate", "commission_rate");
this.frm.add_fetch("sales_person", "commission_rate", "commission_rate");
} }
onload() { onload() {
@ -514,4 +512,4 @@ frappe.ui.form.on(cur_frm.doctype, {
dialog.show(); dialog.show();
} }
}) })

View File

@ -135,8 +135,8 @@ class AuthorizationControl(TransactionBase):
price_list_rate, base_rate = 0, 0 price_list_rate, base_rate = 0, 0
for d in doc_obj.get("items"): for d in doc_obj.get("items"):
if d.base_rate: if d.base_rate:
price_list_rate += flt(d.base_price_list_rate) or flt(d.base_rate) price_list_rate += (flt(d.base_price_list_rate) or flt(d.base_rate)) * flt(d.qty)
base_rate += flt(d.base_rate) base_rate += flt(d.base_rate) * flt(d.qty)
if doc_obj.get("discount_amount"): if doc_obj.get("discount_amount"):
base_rate -= flt(doc_obj.discount_amount) base_rate -= flt(doc_obj.discount_amount)

View File

@ -25,7 +25,7 @@
"documentation_url": "https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/company-setup", "documentation_url": "https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/company-setup",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"modified": "2021-12-15 14:23:52.460913", "modified": "2022-06-07 14:31:00.575193",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Home", "name": "Home",

View File

@ -2,14 +2,14 @@
"action": "Watch Video", "action": "Watch Video",
"action_label": "Learn more about data migration", "action_label": "Learn more about data migration",
"creation": "2021-05-19 05:29:16.809610", "creation": "2021-05-19 05:29:16.809610",
"description": "# Import Data from Spreadsheet\n\nIn ERPNext, you can easily migrate your historical data using spreadsheets. You can use it for migrating not just masters (like Customer, Supplier, Items), but also for transactions like (outstanding invoices, opening stock and accounting entries, etc). If you are migrating from [Tally](https://tallysolutions.com/) or [Quickbooks](https://quickbooks.intuit.com/in/), we got special migration tools for you.", "description": "# Import Data from Spreadsheet\n\nIn ERPNext, you can easily migrate your historical data using spreadsheets. You can use it for migrating not just masters (like Customer, Supplier, Items), but also for transactions like (outstanding invoices, opening stock and accounting entries, etc).",
"docstatus": 0, "docstatus": 0,
"doctype": "Onboarding Step", "doctype": "Onboarding Step",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"is_single": 0, "is_single": 0,
"is_skipped": 0, "is_skipped": 0,
"modified": "2021-12-15 13:10:57.346422", "modified": "2022-06-07 14:28:51.390813",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Data import", "name": "Data import",
"owner": "Administrator", "owner": "Administrator",

View File

@ -9,7 +9,7 @@
"is_complete": 0, "is_complete": 0,
"is_single": 0, "is_single": 0,
"is_skipped": 0, "is_skipped": 0,
"modified": "2021-12-15 14:20:55.441678", "modified": "2022-06-07 14:28:00.901082",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Navigation Help", "name": "Navigation Help",
"owner": "Administrator", "owner": "Administrator",

View File

@ -1192,6 +1192,8 @@
"width": "50%" "width": "50%"
}, },
{ {
"fetch_from": "sales_partner.commission_rate",
"fetch_if_empty": 1,
"fieldname": "commission_rate", "fieldname": "commission_rate",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Commission Rate (%)", "label": "Commission Rate (%)",
@ -1334,7 +1336,7 @@
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-04-26 14:48:08.781837", "modified": "2022-06-10 03:52:04.197415",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",

View File

@ -11,7 +11,7 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"name_and_description_section", "details",
"naming_series", "naming_series",
"item_code", "item_code",
"variant_of", "variant_of",
@ -35,11 +35,11 @@
"over_billing_allowance", "over_billing_allowance",
"image", "image",
"section_break_11", "section_break_11",
"brand",
"description", "description",
"sb_barcodes", "brand",
"barcodes", "dashboard_tab",
"inventory_section", "inventory_section",
"inventory_settings_section",
"shelf_life_in_days", "shelf_life_in_days",
"end_of_life", "end_of_life",
"default_material_request_type", "default_material_request_type",
@ -49,6 +49,8 @@
"weight_per_unit", "weight_per_unit",
"weight_uom", "weight_uom",
"allow_negative_stock", "allow_negative_stock",
"sb_barcodes",
"barcodes",
"reorder_section", "reorder_section",
"reorder_levels", "reorder_levels",
"unit_of_measure_conversion", "unit_of_measure_conversion",
@ -67,13 +69,13 @@
"has_variants", "has_variants",
"variant_based_on", "variant_based_on",
"attributes", "attributes",
"defaults", "accounting",
"item_defaults", "item_defaults",
"purchase_details", "purchasing_tab",
"is_purchase_item",
"purchase_uom", "purchase_uom",
"min_order_qty", "min_order_qty",
"safety_stock", "safety_stock",
"is_purchase_item",
"purchase_details_cb", "purchase_details_cb",
"lead_time_days", "lead_time_days",
"last_purchase_rate", "last_purchase_rate",
@ -83,33 +85,31 @@
"delivered_by_supplier", "delivered_by_supplier",
"column_break2", "column_break2",
"supplier_items", "supplier_items",
"deferred_expense_section",
"enable_deferred_expense",
"deferred_expense_account",
"no_of_months_exp",
"foreign_trade_details", "foreign_trade_details",
"country_of_origin", "country_of_origin",
"column_break_59", "column_break_59",
"customs_tariff_number", "customs_tariff_number",
"sales_details", "sales_details",
"sales_uom", "sales_uom",
"is_sales_item",
"grant_commission", "grant_commission",
"is_sales_item",
"column_break3", "column_break3",
"max_discount", "max_discount",
"deferred_revenue", "deferred_revenue",
"deferred_revenue_account",
"enable_deferred_revenue", "enable_deferred_revenue",
"column_break_85", "deferred_revenue_account",
"no_of_months", "no_of_months",
"deferred_expense_section",
"deferred_expense_account",
"enable_deferred_expense",
"column_break_88",
"no_of_months_exp",
"customer_details", "customer_details",
"customer_items", "customer_items",
"item_tax_section_break", "item_tax_section_break",
"taxes", "taxes",
"inspection_criteria", "quality_tab",
"quality_inspection_template",
"inspection_required_before_purchase", "inspection_required_before_purchase",
"quality_inspection_template",
"inspection_required_before_delivery", "inspection_required_before_delivery",
"manufacturing", "manufacturing",
"default_bom", "default_bom",
@ -118,17 +118,10 @@
"customer_code", "customer_code",
"default_item_manufacturer", "default_item_manufacturer",
"default_manufacturer_part_no", "default_manufacturer_part_no",
"more_information_section",
"published_in_website", "published_in_website",
"total_projected_qty" "total_projected_qty"
], ],
"fields": [ "fields": [
{
"fieldname": "name_and_description_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
"options": "fa fa-flag"
},
{ {
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
@ -315,7 +308,7 @@
"collapsible_depends_on": "is_stock_item", "collapsible_depends_on": "is_stock_item",
"depends_on": "is_stock_item", "depends_on": "is_stock_item",
"fieldname": "inventory_section", "fieldname": "inventory_section",
"fieldtype": "Section Break", "fieldtype": "Tab Break",
"label": "Inventory", "label": "Inventory",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "fa fa-truck" "options": "fa fa-truck"
@ -514,31 +507,17 @@
"label": "Attributes", "label": "Attributes",
"options": "Item Variant Attribute" "options": "Item Variant Attribute"
}, },
{
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "defaults",
"fieldtype": "Section Break",
"label": "Sales, Purchase, Accounting Defaults"
},
{ {
"fieldname": "item_defaults", "fieldname": "item_defaults",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Item Defaults", "label": "Item Defaults",
"options": "Item Default" "options": "Item Default"
}, },
{
"collapsible": 1,
"fieldname": "purchase_details",
"fieldtype": "Section Break",
"label": "Purchase, Replenishment Details",
"oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart"
},
{ {
"default": "1", "default": "1",
"fieldname": "is_purchase_item", "fieldname": "is_purchase_item",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Purchase Item" "label": "Allow Purchase"
}, },
{ {
"fieldname": "purchase_uom", "fieldname": "purchase_uom",
@ -646,8 +625,8 @@
{ {
"collapsible": 1, "collapsible": 1,
"fieldname": "sales_details", "fieldname": "sales_details",
"fieldtype": "Section Break", "fieldtype": "Tab Break",
"label": "Sales Details", "label": "Sales",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "fa fa-tag" "options": "fa fa-tag"
}, },
@ -661,7 +640,7 @@
"default": "1", "default": "1",
"fieldname": "is_sales_item", "fieldname": "is_sales_item",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Sales Item" "label": "Allow Sales"
}, },
{ {
"fieldname": "column_break3", "fieldname": "column_break3",
@ -696,10 +675,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Enable Deferred Revenue" "label": "Enable Deferred Revenue"
}, },
{
"fieldname": "column_break_85",
"fieldtype": "Column Break"
},
{ {
"depends_on": "enable_deferred_revenue", "depends_on": "enable_deferred_revenue",
"fieldname": "no_of_months", "fieldname": "no_of_months",
@ -726,10 +701,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Enable Deferred Expense" "label": "Enable Deferred Expense"
}, },
{
"fieldname": "column_break_88",
"fieldtype": "Column Break"
},
{ {
"depends_on": "enable_deferred_expense", "depends_on": "enable_deferred_expense",
"fieldname": "no_of_months_exp", "fieldname": "no_of_months_exp",
@ -753,8 +724,8 @@
"collapsible": 1, "collapsible": 1,
"collapsible_depends_on": "taxes", "collapsible_depends_on": "taxes",
"fieldname": "item_tax_section_break", "fieldname": "item_tax_section_break",
"fieldtype": "Section Break", "fieldtype": "Tab Break",
"label": "Item Tax", "label": "Tax",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "fa fa-money" "options": "fa fa-money"
}, },
@ -767,15 +738,6 @@
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Item Tax" "options": "Item Tax"
}, },
{
"collapsible": 1,
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "inspection_criteria",
"fieldtype": "Section Break",
"label": "Inspection Criteria",
"oldfieldtype": "Section Break",
"options": "fa fa-search"
},
{ {
"default": "0", "default": "0",
"fieldname": "inspection_required_before_purchase", "fieldname": "inspection_required_before_purchase",
@ -801,7 +763,7 @@
"collapsible": 1, "collapsible": 1,
"depends_on": "is_stock_item", "depends_on": "is_stock_item",
"fieldname": "manufacturing", "fieldname": "manufacturing",
"fieldtype": "Section Break", "fieldtype": "Tab Break",
"label": "Manufacturing", "label": "Manufacturing",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "fa fa-cogs" "options": "fa fa-cogs"
@ -880,12 +842,6 @@
"label": "Default Manufacturer Part No", "label": "Default Manufacturer Part No",
"read_only": 1 "read_only": 1
}, },
{
"collapsible": 1,
"fieldname": "more_information_section",
"fieldtype": "Section Break",
"label": "More Information"
},
{ {
"default": "0", "default": "0",
"depends_on": "published_in_website", "depends_on": "published_in_website",
@ -912,6 +868,40 @@
"fieldname": "allow_negative_stock", "fieldname": "allow_negative_stock",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Allow Negative Stock" "label": "Allow Negative Stock"
},
{
"fieldname": "inventory_settings_section",
"fieldtype": "Section Break",
"label": "Inventory Settings"
},
{
"fieldname": "purchasing_tab",
"fieldtype": "Tab Break",
"label": "Purchasing"
},
{
"fieldname": "quality_tab",
"fieldtype": "Tab Break",
"label": "Quality"
},
{
"fieldname": "details",
"fieldtype": "Tab Break",
"label": "Details",
"oldfieldtype": "Section Break",
"options": "fa fa-flag"
},
{
"fieldname": "dashboard_tab",
"fieldtype": "Tab Break",
"label": "Dashboard",
"show_dashboard": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "accounting",
"fieldtype": "Tab Break",
"label": "Accounting"
} }
], ],
"icon": "fa fa-tag", "icon": "fa fa-tag",
@ -919,7 +909,7 @@
"image_field": "image", "image_field": "image",
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2022-04-28 04:52:10.272256", "modified": "2022-06-08 11:35:20.094546",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@ -4297,7 +4297,7 @@ Fetch Serial Numbers based on FIFO,إحضار الأرقام المسلسلة ب
"To allow different rates, disable the {0} checkbox in {1}.",للسماح بمعدلات مختلفة ، قم بتعطيل مربع الاختيار {0} في {1}., "To allow different rates, disable the {0} checkbox in {1}.",للسماح بمعدلات مختلفة ، قم بتعطيل مربع الاختيار {0} في {1}.,
Current Odometer Value should be greater than Last Odometer Value {0},يجب أن تكون قيمة عداد المسافات الحالية أكبر من قيمة آخر عداد المسافات {0}, Current Odometer Value should be greater than Last Odometer Value {0},يجب أن تكون قيمة عداد المسافات الحالية أكبر من قيمة آخر عداد المسافات {0},
No additional expenses has been added,لم يتم إضافة مصاريف إضافية, No additional expenses has been added,لم يتم إضافة مصاريف إضافية,
Asset{} {assets_link} created for {},الأصل {} {asset_link} الذي تم إنشاؤه لـ {}, Asset{} {assets_link} created for {},الأصل {} {assets_link} الذي تم إنشاؤه لـ {},
Row {}: Asset Naming Series is mandatory for the auto creation for item {},الصف {}: سلسلة تسمية الأصول إلزامية للإنشاء التلقائي للعنصر {}, Row {}: Asset Naming Series is mandatory for the auto creation for item {},الصف {}: سلسلة تسمية الأصول إلزامية للإنشاء التلقائي للعنصر {},
Assets not created for {0}. You will have to create asset manually.,لم يتم إنشاء الأصول لـ {0}. سيكون عليك إنشاء الأصل يدويًا., Assets not created for {0}. You will have to create asset manually.,لم يتم إنشاء الأصول لـ {0}. سيكون عليك إنشاء الأصل يدويًا.,
{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.,{0} يحتوي {1} على إدخالات محاسبية بالعملة {2} للشركة {3}. الرجاء تحديد حساب مستحق أو دائن بالعملة {2}., {0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.,{0} يحتوي {1} على إدخالات محاسبية بالعملة {2} للشركة {3}. الرجاء تحديد حساب مستحق أو دائن بالعملة {2}.,

Can't render this file because it is too large.

View File

@ -288,7 +288,7 @@ Asset {0} must be submitted,Актив {0} должен быть проведе
Assets,Активы, Assets,Активы,
Assign,Назначить, Assign,Назначить,
Assign Salary Structure,Назначить структуру заработной платы, Assign Salary Structure,Назначить структуру заработной платы,
Assign To,Назначить в, Assign To,Назначить для,
Assign to Employees,Назначить сотрудникам, Assign to Employees,Назначить сотрудникам,
Assigning Structures...,Назначение структур..., Assigning Structures...,Назначение структур...,
Associate,Помощник, Associate,Помощник,
@ -421,7 +421,7 @@ Buildings,Здания,
Bundle items at time of sale.,Собирать продукты в момент продажи., Bundle items at time of sale.,Собирать продукты в момент продажи.,
Business Development Manager,Менеджер по развитию бизнеса, Business Development Manager,Менеджер по развитию бизнеса,
Buy,Купить, Buy,Купить,
Buying,Покупки, Buying,Закупки,
Buying Amount,Сумма покупки, Buying Amount,Сумма покупки,
Buying Price List,Ценовой список покупок, Buying Price List,Ценовой список покупок,
Buying Rate,Частота покупки, Buying Rate,Частота покупки,
@ -490,7 +490,7 @@ Capital Equipments,Капитальные оборудование,
Capital Stock,Капитал, Capital Stock,Капитал,
Capital Work in Progress,Капитальная работа в процессе, Capital Work in Progress,Капитальная работа в процессе,
Cart,Корзина, Cart,Корзина,
Cart is Empty,Корзина Пусто, Cart is Empty,Корзина пуста,
Case No(s) already in use. Try from Case No {0},Случай Нет (ы) уже используется. Попробуйте из дела № {0}, Case No(s) already in use. Try from Case No {0},Случай Нет (ы) уже используется. Попробуйте из дела № {0},
Cash,Наличные, Cash,Наличные,
Cash Flow Statement,О движении денежных средств, Cash Flow Statement,О движении денежных средств,
@ -578,7 +578,7 @@ Compensatory Off,Компенсационные Выкл,
Compensatory leave request days not in valid holidays,Дни запроса на получение компенсационных отчислений не действительны, Compensatory leave request days not in valid holidays,Дни запроса на получение компенсационных отчислений не действительны,
Complaint,Жалоба, Complaint,Жалоба,
Completion Date,Дата завершения, Completion Date,Дата завершения,
Computer,компьютер, Computer,Компьютер,
Condition,Условия, Condition,Условия,
Configure,Конфигурировать, Configure,Конфигурировать,
Configure {0},Настроить {0}, Configure {0},Настроить {0},
@ -643,7 +643,6 @@ Course Code: ,Код курса: ,
Course Enrollment {0} does not exists,Зачисление на курс {0} не существует, Course Enrollment {0} does not exists,Зачисление на курс {0} не существует,
Course Schedule,Расписание курса, Course Schedule,Расписание курса,
Course: ,Курс: , Course: ,Курс: ,
Cr,Cr,
Create,Создать, Create,Создать,
Create BOM,Создать спецификацию, Create BOM,Создать спецификацию,
Create Delivery Trip,Создать маршрут доставки, Create Delivery Trip,Создать маршрут доставки,
@ -795,7 +794,6 @@ Defense,Оборона,
Define Project type.,Установите тип проекта., Define Project type.,Установите тип проекта.,
Define budget for a financial year.,Определить бюджет на финансовый год., Define budget for a financial year.,Определить бюджет на финансовый год.,
Define various loan types,Определение различных видов кредита, Define various loan types,Определение различных видов кредита,
Del,Del,
Delay in payment (Days),Задержка в оплате (дни), Delay in payment (Days),Задержка в оплате (дни),
Delete all the Transactions for this Company,Удалить все транзакции этой компании, Delete all the Transactions for this Company,Удалить все транзакции этой компании,
Deletion is not permitted for country {0},Для страны не разрешено удаление {0}, Deletion is not permitted for country {0},Для страны не разрешено удаление {0},
@ -1287,12 +1285,12 @@ Installing presets,Установка пресетов,
Institute Abbreviation,институт Аббревиатура, Institute Abbreviation,институт Аббревиатура,
Institute Name,Название института, Institute Name,Название института,
Instructor,Инструктор, Instructor,Инструктор,
Insufficient Stock,Недостаточный Stock, Insufficient Stock,Недостаточный запас,
Insurance Start date should be less than Insurance End date,"Дата страхование начала должна быть меньше, чем дата страхование End", Insurance Start date should be less than Insurance End date,"Дата начала страхования должна быть раньше, чем дата окончания",
Integrated Tax,Интегрированный налог, Integrated Tax,Интегрированный налог,
Inter-State Supplies,Межгосударственные поставки, Inter-State Supplies,Межгосударственные поставки,
Interest Amount,Проценты Сумма, Interest Amount,Сумма процентов,
Interests,интересы, Interests,Интересы,
Intern,Стажер, Intern,Стажер,
Internet Publishing,Интернет издания, Internet Publishing,Интернет издания,
Intra-State Supplies,Внутригосударственные поставки, Intra-State Supplies,Внутригосударственные поставки,
@ -1397,7 +1395,7 @@ Job Card,Карточка работы,
Job Description,Описание работы, Job Description,Описание работы,
Job Offer,Предложение работы, Job Offer,Предложение работы,
Job card {0} created,Карта работы {0} создана, Job card {0} created,Карта работы {0} создана,
Jobs,Работы, Jobs,Вакансии,
Join,Присоединиться, Join,Присоединиться,
Journal Entries {0} are un-linked,Записи в журнале {0} не-связаны, Journal Entries {0} are un-linked,Записи в журнале {0} не-связаны,
Journal Entry,Запись в журнале, Journal Entry,Запись в журнале,
@ -1925,7 +1923,7 @@ Pending Amount,В ожидании Сумма,
Pending Leaves,Ожидающие листья, Pending Leaves,Ожидающие листья,
Pending Qty,В ожидании кол-во, Pending Qty,В ожидании кол-во,
Pending Quantity,Количество в ожидании, Pending Quantity,Количество в ожидании,
Pending Review,В ожидании отзыв, Pending Review,В ожидании отзыва,
Pending activities for today,В ожидании деятельность на сегодняшний день, Pending activities for today,В ожидании деятельность на сегодняшний день,
Pension Funds,Пенсионные фонды, Pension Funds,Пенсионные фонды,
Percentage Allocation should be equal to 100%,Процент Распределение должно быть равно 100%, Percentage Allocation should be equal to 100%,Процент Распределение должно быть равно 100%,
@ -1949,7 +1947,7 @@ Planned Qty,Планируемое кол-во,
Planning,Планирование, Planning,Планирование,
Plants and Machineries,Растения и Механизмов, Plants and Machineries,Растения и Механизмов,
Please Set Supplier Group in Buying Settings.,Установите группу поставщиков в разделе «Настройки покупок»., Please Set Supplier Group in Buying Settings.,Установите группу поставщиков в разделе «Настройки покупок».,
Please add a Temporary Opening account in Chart of Accounts,"Пожалуйста, добавьте временный вступительный счет в План счетов", Please add a Temporary Opening account in Chart of Accounts,"Пожалуйста, добавьте временный вступительный счет в план счетов",
Please add the account to root level Company - ,"Пожалуйста, добавьте счет на корневой уровень компании -", Please add the account to root level Company - ,"Пожалуйста, добавьте счет на корневой уровень компании -",
Please add the remaining benefits {0} to any of the existing component,Добавьте оставшиеся преимущества {0} к любому из существующих компонентов, Please add the remaining benefits {0} to any of the existing component,Добавьте оставшиеся преимущества {0} к любому из существующих компонентов,
Please check Multi Currency option to allow accounts with other currency,"Пожалуйста, проверьте мультивалютный вариант, позволяющий счета другой валюте", Please check Multi Currency option to allow accounts with other currency,"Пожалуйста, проверьте мультивалютный вариант, позволяющий счета другой валюте",
@ -2146,7 +2144,7 @@ Preview Salary Slip,Просмотр Зарплата скольжению,
Previous Financial Year is not closed,Предыдущий финансовый год не закрыт, Previous Financial Year is not closed,Предыдущий финансовый год не закрыт,
Price,Цена, Price,Цена,
Price List,Прайс-лист, Price List,Прайс-лист,
Price List Currency not selected,Прайс-лист Обмен не выбран, Price List Currency not selected,Валюта прайс-листа не выбрана,
Price List Rate,Прайс-лист Оценить, Price List Rate,Прайс-лист Оценить,
Price List master.,Мастер Прайс-лист., Price List master.,Мастер Прайс-лист.,
Price List must be applicable for Buying or Selling,Прайс-лист должен быть применим для покупки или продажи, Price List must be applicable for Buying or Selling,Прайс-лист должен быть применим для покупки или продажи,
@ -2347,7 +2345,7 @@ Remaining,Осталось,
Remaining Balance,Остаток средств, Remaining Balance,Остаток средств,
Remarks,Примечания, Remarks,Примечания,
Reminder to update GSTIN Sent,Напоминание об обновлении отправленного GSTIN, Reminder to update GSTIN Sent,Напоминание об обновлении отправленного GSTIN,
Remove item if charges is not applicable to that item,"Удалить продукт, если сборы не применимы к этому продукту", Remove item if charges is not applicable to that item,"Удалить объект, если к нему не применяются сборы",
Removed items with no change in quantity or value.,Удалены пункты без изменения в количестве или стоимости., Removed items with no change in quantity or value.,Удалены пункты без изменения в количестве или стоимости.,
Reopen,Возобновить, Reopen,Возобновить,
Reorder Level,Уровень переупорядочения, Reorder Level,Уровень переупорядочения,
@ -2509,7 +2507,7 @@ Salary Slip of employee {0} already created for this period,Зарплата С
Salary Slip of employee {0} already created for time sheet {1},Зарплата Скольжение работника {0} уже создан для табеля {1}, Salary Slip of employee {0} already created for time sheet {1},Зарплата Скольжение работника {0} уже создан для табеля {1},
Salary Slip submitted for period from {0} to {1},"Зарплатная ведомость отправлена за период с {0} по {1}", Salary Slip submitted for period from {0} to {1},"Зарплатная ведомость отправлена за период с {0} по {1}",
Salary Structure Assignment for Employee already exists,Присвоение структуры зарплаты сотруднику уже существует, Salary Structure Assignment for Employee already exists,Присвоение структуры зарплаты сотруднику уже существует,
Salary Structure Missing,Структура заработной платы Отсутствующий, Salary Structure Missing,Структура заработной платы отсутствует,
Salary Structure must be submitted before submission of Tax Ememption Declaration,Структура заработной платы должна быть представлена до подачи декларации об освобождении от налогов, Salary Structure must be submitted before submission of Tax Ememption Declaration,Структура заработной платы должна быть представлена до подачи декларации об освобождении от налогов,
Salary Structure not found for employee {0} and date {1},Структура зарплаты не найдена для сотрудника {0} и даты {1}, Salary Structure not found for employee {0} and date {1},Структура зарплаты не найдена для сотрудника {0} и даты {1},
Salary Structure should have flexible benefit component(s) to dispense benefit amount,Структура заработной платы должна иметь гибкий компонент (ы) выгоды для распределения суммы пособия, Salary Structure should have flexible benefit component(s) to dispense benefit amount,Структура заработной платы должна иметь гибкий компонент (ы) выгоды для распределения суммы пособия,
@ -2701,10 +2699,10 @@ Setup default values for POS Invoices,Настройка значений по
Setup mode of POS (Online / Offline),Режим настройки POS (Online / Offline), Setup mode of POS (Online / Offline),Режим настройки POS (Online / Offline),
Setup your Institute in ERPNext,Установите свой институт в ERPNext, Setup your Institute in ERPNext,Установите свой институт в ERPNext,
Share Balance,Баланс акций, Share Balance,Баланс акций,
Share Ledger,Поделиться записями, Share Ledger,Записи по акциям,
Share Management,Управление долями, Share Management,Управление долями,
Share Transfer,Передача акций, Share Transfer,Передача акций,
Share Type,Share Тип, Share Type,Тип акций,
Shareholder,Акционер, Shareholder,Акционер,
Ship To State,Корабль в штат, Ship To State,Корабль в штат,
Shipments,Поставки, Shipments,Поставки,
@ -2796,8 +2794,8 @@ Stock Entry {0} is not submitted,Складской акт {0} не провед
Stock Expenses,Расходы по Запасам, Stock Expenses,Расходы по Запасам,
Stock In Hand,Запасы на руках, Stock In Hand,Запасы на руках,
Stock Items,Позиции на складе, Stock Items,Позиции на складе,
Stock Ledger,Книга учета Запасов, Stock Ledger,Книга учета запасов,
Stock Ledger Entries and GL Entries are reposted for the selected Purchase Receipts,Записи складской книги и записи GL запасов отправляются для выбранных покупок, Stock Ledger Entries and GL Entries are reposted for the selected Purchase Receipts,Записи книги учета запасов и записи GL повторно публикуются для выбранных квитанций о покупках,
Stock Levels,Уровень запасов, Stock Levels,Уровень запасов,
Stock Liabilities,Обязательства по запасам, Stock Liabilities,Обязательства по запасам,
Stock Options,Опционы, Stock Options,Опционы,
@ -2829,9 +2827,9 @@ Student Email ID,Идентификация студента по электро
Student Group,Учебная группа, Student Group,Учебная группа,
Student Group Strength,Сила студенческой группы, Student Group Strength,Сила студенческой группы,
Student Group is already updated.,Студенческая группа уже обновлена., Student Group is already updated.,Студенческая группа уже обновлена.,
Student Group: ,Студенческая группа:, Student Group: ,Студенческая группа: ,
Student ID,Студенческий билет, Student ID,Студенческий билет,
Student ID: ,Студенческий билет:, Student ID: ,Студенческий билет: ,
Student LMS Activity,Студенческая LMS Активность, Student LMS Activity,Студенческая LMS Активность,
Student Mobile No.,Мобильный номер студента, Student Mobile No.,Мобильный номер студента,
Student Name,Имя ученика, Student Name,Имя ученика,
@ -2864,9 +2862,9 @@ Successfully created payment entries,Успешно созданные плат
Successfully deleted all transactions related to this company!,"Успешно удален все сделки, связанные с этой компанией!", Successfully deleted all transactions related to this company!,"Успешно удален все сделки, связанные с этой компанией!",
Sum of Scores of Assessment Criteria needs to be {0}.,Сумма десятков критериев оценки должно быть {0}., Sum of Scores of Assessment Criteria needs to be {0}.,Сумма десятков критериев оценки должно быть {0}.,
Sum of points for all goals should be 100. It is {0},Сумма баллов за все цели должны быть 100. Это {0}, Sum of points for all goals should be 100. It is {0},Сумма баллов за все цели должны быть 100. Это {0},
Summary,Резюме, Summary,Сводка,
Summary for this month and pending activities,Резюме для этого месяца и в ожидании деятельности, Summary for this month and pending activities,Сводка за этот месяц и предстоящие мероприятия,
Summary for this week and pending activities,Резюме на этой неделе и в ожидании деятельности, Summary for this week and pending activities,Сводка за эту неделю и предстоящие мероприятия,
Sunday,Воскресенье, Sunday,Воскресенье,
Suplier,Поставщик, Suplier,Поставщик,
Supplier,Поставщик, Supplier,Поставщик,
@ -2880,7 +2878,7 @@ Supplier Name,наименование поставщика,
Supplier Part No,Деталь поставщика №, Supplier Part No,Деталь поставщика №,
Supplier Quotation,Предложение поставщика, Supplier Quotation,Предложение поставщика,
Supplier Scorecard,Оценочная карта поставщика, Supplier Scorecard,Оценочная карта поставщика,
Supplier Warehouse mandatory for sub-contracted Purchase Receipt,Поставщик Склад обязательным для субподрядчиком ТОВАРНЫЙ ЧЕК, Supplier Warehouse mandatory for sub-contracted Purchase Receipt,Наличие склада поставщика обязательно для субподрядной квитанции о покупке,
Supplier database.,База данных поставщиков., Supplier database.,База данных поставщиков.,
Supplier {0} not found in {1},Поставщик {0} не найден в {1}, Supplier {0} not found in {1},Поставщик {0} не найден в {1},
Supplier(s),Поставщик(и), Supplier(s),Поставщик(и),
@ -3199,7 +3197,7 @@ Used Leaves,Используемые листы,
User,Пользователь, User,Пользователь,
User ID,ID пользователя, User ID,ID пользователя,
User ID not set for Employee {0},ID пользователя не установлен для сотрудника {0}, User ID not set for Employee {0},ID пользователя не установлен для сотрудника {0},
User Remark,Примечание Пользователь, User Remark,Примечание пользователя,
User has not applied rule on the invoice {0},Пользователь не применил правило к счету {0}, User has not applied rule on the invoice {0},Пользователь не применил правило к счету {0},
User {0} already exists,Пользователь {0} уже существует, User {0} already exists,Пользователь {0} уже существует,
User {0} created,Пользователь {0} создан, User {0} created,Пользователь {0} создан,
@ -3243,7 +3241,7 @@ View Fees Records,Посмотреть рекорды,
View Form,Посмотреть форму, View Form,Посмотреть форму,
View Lab Tests,Просмотр лабораторных тестов, View Lab Tests,Просмотр лабораторных тестов,
View Leads,Посмотреть лиды, View Leads,Посмотреть лиды,
View Ledger,Посмотреть Леджер, View Ledger,Посмотреть записи,
View Now,Просмотр сейчас, View Now,Просмотр сейчас,
View a list of all the help videos,Просмотреть список всех справочных видео, View a list of all the help videos,Просмотреть список всех справочных видео,
View in Cart,Смотрите в корзину, View in Cart,Смотрите в корзину,
@ -3314,7 +3312,7 @@ Work Orders Created: {0},Созданы рабочие задания: {0},
Work Summary for {0},Резюме работы для {0}, Work Summary for {0},Резюме работы для {0},
Work-in-Progress Warehouse is required before Submit,Работа-в-Прогресс Склад требуется перед Отправить, Work-in-Progress Warehouse is required before Submit,Работа-в-Прогресс Склад требуется перед Отправить,
Workflow,Рабочий процесс, Workflow,Рабочий процесс,
Working,Работающий, Working,В работе,
Working Hours,Часы работы, Working Hours,Часы работы,
Workstation,Рабочее место, Workstation,Рабочее место,
Workstation is closed on the following dates as per Holiday List: {0},Рабочая место закрыто в следующие даты согласно списка праздников: {0}, Workstation is closed on the following dates as per Holiday List: {0},Рабочая место закрыто в следующие даты согласно списка праздников: {0},
@ -3869,13 +3867,17 @@ Non stock items,Нет на складе,
Not Allowed,Не разрешено, Not Allowed,Не разрешено,
Not allowed to create accounting dimension for {0},Не разрешено создавать учетное измерение для {0}, Not allowed to create accounting dimension for {0},Не разрешено создавать учетное измерение для {0},
Not permitted. Please disable the Lab Test Template,"Не разрешено Пожалуйста, отключите шаблон лабораторного теста", Not permitted. Please disable the Lab Test Template,"Не разрешено Пожалуйста, отключите шаблон лабораторного теста",
Note,Заметки, Note,Заметка,
Notes: ,Заметки: , Notes: ,Заметки: ,
On Converting Opportunity,О возможности конвертации, On Converting Opportunity,Конвертацию возможности,
On Purchase Order Submission,При подаче заказа на поставку, On Purchase Order Submission,Офомление заказа на закупку,
On Sales Order Submission,На подаче заказа клиента, On Sales Order Submission,Оформление заказа на продажу,
On Task Completion,По завершении задачи, On Task Completion,Завершении задачи,
On {0} Creation,На {0} создании, On {0} Creation,На {0} создании,
On Item Creation,Создание продукта,
On Lead Creation,Создание лида,
On Supplier Creation,Создание поставщика,
On Customer Creation,Создание клиента,
Only .csv and .xlsx files are supported currently,В настоящее время поддерживаются только файлы .csv и .xlsx, Only .csv and .xlsx files are supported currently,В настоящее время поддерживаются только файлы .csv и .xlsx,
Only expired allocation can be cancelled,Только истекшее распределение может быть отменено, Only expired allocation can be cancelled,Только истекшее распределение может быть отменено,
Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия, Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия,
@ -4217,7 +4219,7 @@ Mode Of Payment,Способ оплаты,
No students Found,Студенты не найдены, No students Found,Студенты не найдены,
Not in Stock,Нет в наличии, Not in Stock,Нет в наличии,
Please select a Customer,Выберите клиента, Please select a Customer,Выберите клиента,
Printed On,Отпечатано на, Printed On,Напечатано на,
Received From,Получено от, Received From,Получено от,
Sales Person,Продавец, Sales Person,Продавец,
To date cannot be before From date,На сегодняшний день не может быть раньше От даты, To date cannot be before From date,На сегодняшний день не может быть раньше От даты,
@ -4945,14 +4947,14 @@ Max Qty,Макс. кол-во,
Min Amt,Мин Amt, Min Amt,Мин Amt,
Max Amt,Макс Амт, Max Amt,Макс Амт,
Period Settings,Настройки периода, Period Settings,Настройки периода,
Margin,Разница, Margin,Маржа,
Margin Type,Тип маржа, Margin Type,Тип маржа,
Margin Rate or Amount,Маржинальная ставка или сумма, Margin Rate or Amount,Маржинальная ставка или сумма,
Price Discount Scheme,Схема скидок, Price Discount Scheme,Схема скидок,
Rate or Discount,Стоимость или скидка, Rate or Discount,Стоимость или скидка,
Discount Percentage,Скидка в процентах, Discount Percentage,Скидка в процентах,
Discount Amount,Сумма скидки, Discount Amount,Сумма скидки,
For Price List,Для Прейскурантом, For Price List,Для прайс-листа,
Product Discount Scheme,Схема скидок на товары, Product Discount Scheme,Схема скидок на товары,
Same Item,Тот же пункт, Same Item,Тот же пункт,
Free Item,Бесплатный товар, Free Item,Бесплатный товар,
@ -5385,18 +5387,18 @@ Insurance Start Date,Дата начала страхования,
Insurance End Date,Дата окончания страхования, Insurance End Date,Дата окончания страхования,
Comprehensive Insurance,Комплексное страхование, Comprehensive Insurance,Комплексное страхование,
Maintenance Required,Требуется техническое обслуживание, Maintenance Required,Требуется техническое обслуживание,
Check if Asset requires Preventive Maintenance or Calibration,"Проверьте, требуется ли Asset профилактическое обслуживание или калибровка", Check if Asset requires Preventive Maintenance or Calibration,"Проверьте, требует ли актив профилактического обслуживания или калибровки",
Booked Fixed Asset,Забронированные основные средства, Booked Fixed Asset,Забронированные основные средства,
Purchase Receipt Amount,Сумма покупки, Purchase Receipt Amount,Сумма покупки,
Default Finance Book,Финансовая книга по умолчанию, Default Finance Book,Финансовая книга по умолчанию,
Quality Manager,Менеджер по качеству, Quality Manager,Менеджер по качеству,
Asset Category Name,Asset Категория Название, Asset Category Name,Название категории активов,
Depreciation Options,Варианты амортизации, Depreciation Options,Варианты амортизации,
Enable Capital Work in Progress Accounting,Включить капитальную работу в процессе учета, Enable Capital Work in Progress Accounting,Включить капитальную работу в процессе учета,
Finance Book Detail,Финансовая книга, Finance Book Detail,Финансовая книга,
Asset Category Account,Счет категории активов, Asset Category Account,Счет категории активов,
Fixed Asset Account,Счет учета основных средств, Fixed Asset Account,Счет учета основных средств,
Accumulated Depreciation Account,Начисленной амортизации Счет, Accumulated Depreciation Account,Счет накопленной амортизации,
Depreciation Expense Account,Износ счет расходов, Depreciation Expense Account,Износ счет расходов,
Capital Work In Progress Account,Счет капитальной работы, Capital Work In Progress Account,Счет капитальной работы,
Asset Finance Book,Финансовая книга по активам, Asset Finance Book,Финансовая книга по активам,
@ -5441,7 +5443,7 @@ Failure Date,Дата отказа,
Assign To Name,Назначить имя, Assign To Name,Назначить имя,
Repair Status,Статус ремонта, Repair Status,Статус ремонта,
Error Description,Описание ошибки, Error Description,Описание ошибки,
Downtime,время простоя, Downtime,Время простоя,
Repair Cost,Стоимость ремонта, Repair Cost,Стоимость ремонта,
Manufacturing Manager,Менеджер производства, Manufacturing Manager,Менеджер производства,
Current Asset Value,Текущая стоимость актива, Current Asset Value,Текущая стоимость актива,
@ -6073,7 +6075,7 @@ Shopify Tax/Shipping Title,Изменить название налога / до
ERPNext Account,Учетная запись ERPNext, ERPNext Account,Учетная запись ERPNext,
Shopify Webhook Detail,Узнайте подробности веб-камеры, Shopify Webhook Detail,Узнайте подробности веб-камеры,
Webhook ID,Идентификатор Webhook, Webhook ID,Идентификатор Webhook,
Tally Migration,Tally Migration, Tally Migration,Tally миграция,
Master Data,Основные данные, Master Data,Основные данные,
"Data exported from Tally that consists of the Chart of Accounts, Customers, Suppliers, Addresses, Items and UOMs","Данные, экспортированные из Tally, которые состоят из плана счетов, клиентов, поставщиков, адресов, позиций и единиц измерения", "Data exported from Tally that consists of the Chart of Accounts, Customers, Suppliers, Addresses, Items and UOMs","Данные, экспортированные из Tally, которые состоят из плана счетов, клиентов, поставщиков, адресов, позиций и единиц измерения",
Is Master Data Processed,Обработка основных данных, Is Master Data Processed,Обработка основных данных,
@ -6082,7 +6084,7 @@ Tally Creditors Account,Счет Tally Creditors,
Creditors Account set in Tally,Счет кредиторов установлен в Tally, Creditors Account set in Tally,Счет кредиторов установлен в Tally,
Tally Debtors Account,Счет Tally должников, Tally Debtors Account,Счет Tally должников,
Debtors Account set in Tally,Счет дебитора установлен в Tally, Debtors Account set in Tally,Счет дебитора установлен в Tally,
Tally Company,Талли Компания, Tally Company,Tally Компания,
Company Name as per Imported Tally Data,Название компании согласно импортированным данным подсчета, Company Name as per Imported Tally Data,Название компании согласно импортированным данным подсчета,
Default UOM,Единица измерения по умолчанию, Default UOM,Единица измерения по умолчанию,
UOM in case unspecified in imported data,"Единицы измерения, если они не указаны в импортированных данных", UOM in case unspecified in imported data,"Единицы измерения, если они не указаны в импортированных данных",
@ -6108,7 +6110,7 @@ Freight and Forwarding Account,Фрахт и пересылка,
Creation User,Создание пользователя, Creation User,Создание пользователя,
"The user that will be used to create Customers, Items and Sales Orders. This user should have the relevant permissions.","Пользователь, который будет использоваться для создания клиентов, товаров и заказов на продажу. Этот пользователь должен иметь соответствующие разрешения.", "The user that will be used to create Customers, Items and Sales Orders. This user should have the relevant permissions.","Пользователь, который будет использоваться для создания клиентов, товаров и заказов на продажу. Этот пользователь должен иметь соответствующие разрешения.",
"This warehouse will be used to create Sales Orders. The fallback warehouse is ""Stores"".",Этот склад будет использоваться для создания заказов на продажу. Резервный склад &quot;Магазины&quot;., "This warehouse will be used to create Sales Orders. The fallback warehouse is ""Stores"".",Этот склад будет использоваться для создания заказов на продажу. Резервный склад &quot;Магазины&quot;.,
"The fallback series is ""SO-WOO-"".",Аварийная серия &quot;SO-WOO-&quot;., "The fallback series is ""SO-WOO-"".","Аварийная серия ""SO-WOO-"".",
This company will be used to create Sales Orders.,Эта компания будет использоваться для создания заказов на продажу., This company will be used to create Sales Orders.,Эта компания будет использоваться для создания заказов на продажу.,
Delivery After (Days),Доставка после (дней), Delivery After (Days),Доставка после (дней),
This is the default offset (days) for the Delivery Date in Sales Orders. The fallback offset is 7 days from the order placement date.,Это смещение по умолчанию (дни) для даты поставки в заказах на продажу. Смещение отступления составляет 7 дней с даты размещения заказа., This is the default offset (days) for the Delivery Date in Sales Orders. The fallback offset is 7 days from the order placement date.,Это смещение по умолчанию (дни) для даты поставки в заказах на продажу. Смещение отступления составляет 7 дней с даты размещения заказа.,
@ -6441,7 +6443,7 @@ Job Applicant,Соискатель работы,
Applicant Name,Имя заявителя, Applicant Name,Имя заявителя,
Appointment Date,Назначенная дата, Appointment Date,Назначенная дата,
Appointment Letter Template,Шаблон письма о назначении, Appointment Letter Template,Шаблон письма о назначении,
Body,Тело, Body,Содержимое,
Closing Notes,Заметки, Closing Notes,Заметки,
Appointment Letter content,Письмо о назначении, Appointment Letter content,Письмо о назначении,
Appraisal,Оценка, Appraisal,Оценка,
@ -6455,7 +6457,7 @@ Appraisal Goal,Цель оценки,
Key Responsibility Area,Основная зона ответственности, Key Responsibility Area,Основная зона ответственности,
Weightage (%),Весовая нагрузка (%), Weightage (%),Весовая нагрузка (%),
Score (0-5),Оценка (0-5), Score (0-5),Оценка (0-5),
Score Earned,Оценка Заработано, Score Earned,Оценка получена,
Appraisal Template Title,Название шаблона оценки, Appraisal Template Title,Название шаблона оценки,
Appraisal Template Goal,Цель шаблона оценки, Appraisal Template Goal,Цель шаблона оценки,
KRA,КРА, KRA,КРА,
@ -6747,7 +6749,7 @@ Applicant Email Address,Адрес электронной почты заяви
Awaiting Response,В ожидании ответа, Awaiting Response,В ожидании ответа,
Job Offer Terms,Условия работы, Job Offer Terms,Условия работы,
Select Terms and Conditions,Выберите Сроки и условия, Select Terms and Conditions,Выберите Сроки и условия,
Printing Details,Печатать Подробности, Printing Details,Подробности печати,
Job Offer Term,Срок действия предложения, Job Offer Term,Срок действия предложения,
Offer Term,Условие предложения, Offer Term,Условие предложения,
Value / Description,Значение / Описание, Value / Description,Значение / Описание,
@ -7520,7 +7522,7 @@ Expected Time (in hours),Ожидаемое время (в часах),
Is Milestone,Является этапом, Is Milestone,Является этапом,
Task Description,Описание задания, Task Description,Описание задания,
Dependencies,Зависимости, Dependencies,Зависимости,
Dependent Tasks,Зависимые задачи, Dependent Tasks,Зависит от задач,
Depends on Tasks,Зависит от задач, Depends on Tasks,Зависит от задач,
Actual Start Date (via Time Sheet),Фактическая дата начала (по табелю учета рабочего времени), Actual Start Date (via Time Sheet),Фактическая дата начала (по табелю учета рабочего времени),
Actual Time (in hours),Фактическое время (в часах), Actual Time (in hours),Фактическое время (в часах),
@ -7645,7 +7647,7 @@ Campaign Schedules,Расписание кампаний,
Buyer of Goods and Services.,Покупатель товаров и услуг., Buyer of Goods and Services.,Покупатель товаров и услуг.,
CUST-.YYYY.-,CUST-.YYYY.-, CUST-.YYYY.-,CUST-.YYYY.-,
Default Company Bank Account,Стандартный банковский счет компании, Default Company Bank Account,Стандартный банковский счет компании,
From Lead,Из Лида, From Lead,Из лида,
Account Manager,Менеджер по работе с клиентами, Account Manager,Менеджер по работе с клиентами,
Allow Sales Invoice Creation Without Sales Order,Разрешить создание счета без заказа на продажу, Allow Sales Invoice Creation Without Sales Order,Разрешить создание счета без заказа на продажу,
Allow Sales Invoice Creation Without Delivery Note,Разрешить создание счета без накладной, Allow Sales Invoice Creation Without Delivery Note,Разрешить создание счета без накладной,
@ -7818,14 +7820,14 @@ Phone No,Номер телефона,
Company Description,Описание компании, Company Description,Описание компании,
Registration Details,Регистрационные данные, Registration Details,Регистрационные данные,
Company registration numbers for your reference. Tax numbers etc.,Регистрационные номера компании для вашей справки. Налоговые числа и т.д., Company registration numbers for your reference. Tax numbers etc.,Регистрационные номера компании для вашей справки. Налоговые числа и т.д.,
Delete Company Transactions,Удалить Сделки Компания, Delete Company Transactions,Удалить транзакции компании,
Currency Exchange,Курс обмена валюты, Currency Exchange,Курс обмена валюты,
Specify Exchange Rate to convert one currency into another,Укажите Курс конвертировать одну валюту в другую, Specify Exchange Rate to convert one currency into another,Укажите Курс конвертировать одну валюту в другую,
From Currency,Из валюты, From Currency,Из валюты,
To Currency,В валюту, To Currency,В валюту,
For Buying,Для покупки, For Buying,Для покупки,
For Selling,Для продажи, For Selling,Для продажи,
Customer Group Name,Группа Имя клиента, Customer Group Name,Название группы клиентов,
Parent Customer Group,Родительская группа клиента, Parent Customer Group,Родительская группа клиента,
Only leaf nodes are allowed in transaction,Только листовые узлы допускаются в сделке, Only leaf nodes are allowed in transaction,Только листовые узлы допускаются в сделке,
Mention if non-standard receivable account applicable,Упоминание если нестандартная задолженность счет применимо, Mention if non-standard receivable account applicable,Упоминание если нестандартная задолженность счет применимо,
@ -7893,7 +7895,7 @@ This is the number of the last created transaction with this prefix,Это чи
Update Series Number,Обновить Идентификаторы по Номеру, Update Series Number,Обновить Идентификаторы по Номеру,
Quotation Lost Reason,Причина Отказа от Предложения, Quotation Lost Reason,Причина Отказа от Предложения,
A third party distributor / dealer / commission agent / affiliate / reseller who sells the companies products for a commission.,"Сторонний дистрибьютер, дилер, агент, филиал или реселлер, который продаёт продукты компании за комиссионное вознаграждение.", A third party distributor / dealer / commission agent / affiliate / reseller who sells the companies products for a commission.,"Сторонний дистрибьютер, дилер, агент, филиал или реселлер, который продаёт продукты компании за комиссионное вознаграждение.",
Sales Partner Name,Имя Партнера по продажам, Sales Partner Name,Имя партнера по продажам,
Partner Type,Тип партнера, Partner Type,Тип партнера,
Address & Contacts,Адрес и контакты, Address & Contacts,Адрес и контакты,
Address Desc,Адрес по убыванию, Address Desc,Адрес по убыванию,
@ -7914,7 +7916,7 @@ Sales Person Targets,Цели продавца,
Set targets Item Group-wise for this Sales Person.,Задайте цели Продуктовых Групп для Продавца, Set targets Item Group-wise for this Sales Person.,Задайте цели Продуктовых Групп для Продавца,
Supplier Group Name,Название группы поставщиков, Supplier Group Name,Название группы поставщиков,
Parent Supplier Group,Родительская группа поставщиков, Parent Supplier Group,Родительская группа поставщиков,
Target Detail,Цель Подробности, Target Detail,Подробности цели,
Target Qty,Целевое количество, Target Qty,Целевое количество,
Target Amount,Целевая сумма, Target Amount,Целевая сумма,
Target Distribution,Распределение цели, Target Distribution,Распределение цели,
@ -7973,13 +7975,13 @@ Is Return,Является Вернуться,
Issue Credit Note,Кредитная кредитная карта, Issue Credit Note,Кредитная кредитная карта,
Return Against Delivery Note,Вернуться На накладной, Return Against Delivery Note,Вернуться На накладной,
Customer's Purchase Order No,Клиентам Заказ Нет, Customer's Purchase Order No,Клиентам Заказ Нет,
Billing Address Name,Адрес для выставления счета Имя, Billing Address Name,Название адреса для выставления счета,
Required only for sample item.,Требуется только для образца пункта., Required only for sample item.,Требуется только для образца пункта.,
"If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в шаблонах Налоги с налогами и сбором платежей, выберите его и нажмите кнопку ниже.", "If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в шаблонах Налоги с налогами и сбором платежей, выберите его и нажмите кнопку ниже.",
In Words will be visible once you save the Delivery Note.,По словам будет виден только вы сохраните накладной., In Words will be visible once you save the Delivery Note.,По словам будет виден только вы сохраните накладной.,
In Words (Export) will be visible once you save the Delivery Note.,В Слов (Экспорт) будут видны только вы сохраните накладной., In Words (Export) will be visible once you save the Delivery Note.,В Слов (Экспорт) будут видны только вы сохраните накладной.,
Transporter Info,Информация для транспортировки, Transporter Info,Информация для транспортировки,
Driver Name,Имя драйвера, Driver Name,Имя водителя,
Track this Delivery Note against any Project,Подписка на Delivery Note против любого проекта, Track this Delivery Note against any Project,Подписка на Delivery Note против любого проекта,
Inter Company Reference,Справочник Интер, Inter Company Reference,Справочник Интер,
Print Without Amount,Распечатать без суммы, Print Without Amount,Распечатать без суммы,
@ -8079,7 +8081,7 @@ Delivered by Supplier (Drop Ship),Доставка поставщиком,
Supplier Items,Продукты поставщика, Supplier Items,Продукты поставщика,
Foreign Trade Details,Сведения о внешней торговле, Foreign Trade Details,Сведения о внешней торговле,
Country of Origin,Страна происхождения, Country of Origin,Страна происхождения,
Sales Details,Продажи Подробности, Sales Details,Детали продажи,
Default Sales Unit of Measure,Единица измерения продаж по умолчанию, Default Sales Unit of Measure,Единица измерения продаж по умолчанию,
Is Sales Item,Продаваемый продукт, Is Sales Item,Продаваемый продукт,
Max Discount (%),Макс. скидка (%), Max Discount (%),Макс. скидка (%),
@ -8117,7 +8119,7 @@ Item Alternative,Альтернативный продукт,
Alternative Item Code,Альтернативный код продукта, Alternative Item Code,Альтернативный код продукта,
Two-way,Двусторонний, Two-way,Двусторонний,
Alternative Item Name,Альтернативное название продукта, Alternative Item Name,Альтернативное название продукта,
Attribute Name,Имя атрибута, Attribute Name,Название атрибута,
Numeric Values,Числовые значения, Numeric Values,Числовые значения,
From Range,От хребта, From Range,От хребта,
Increment,Приращение, Increment,Приращение,
@ -8236,8 +8238,8 @@ Transporter Details,Детали транспорта,
Vehicle Number,Номер транспортного средства, Vehicle Number,Номер транспортного средства,
Vehicle Date,Дата транспортного средства, Vehicle Date,Дата транспортного средства,
Received and Accepted,Получил и принял, Received and Accepted,Получил и принял,
Accepted Quantity,Принято Количество, Accepted Quantity,Количество принятых,
Rejected Quantity,Отклонен Количество, Rejected Quantity,Количество отклоненных,
Accepted Qty as per Stock UOM,Принятое количество в соответствии с единицами измерения запаса, Accepted Qty as per Stock UOM,Принятое количество в соответствии с единицами измерения запаса,
Sample Quantity,Количество образцов, Sample Quantity,Количество образцов,
Rate and Amount,Ставку и сумму, Rate and Amount,Ставку и сумму,
@ -8285,7 +8287,7 @@ Out of AMC,Из КУА,
Warranty Period (Days),Гарантийный срок (дней), Warranty Period (Days),Гарантийный срок (дней),
Serial No Details,Серийный номер подробнее, Serial No Details,Серийный номер подробнее,
MAT-STE-.YYYY.-,MAT-STE-.YYYY.-, MAT-STE-.YYYY.-,MAT-STE-.YYYY.-,
Stock Entry Type,Тип входа, Stock Entry Type,Тип складской записи,
Stock Entry (Outward GIT),Вход в акции (внешний GIT), Stock Entry (Outward GIT),Вход в акции (внешний GIT),
Material Consumption for Manufacture,Потребление материала для производства, Material Consumption for Manufacture,Потребление материала для производства,
Repack,Перепаковать, Repack,Перепаковать,
@ -8447,7 +8449,7 @@ No of Sent SMS,Кол-во отправленных SMS,
Sent To,Отправить, Sent To,Отправить,
Absent Student Report,Отчет о пропуске занятия, Absent Student Report,Отчет о пропуске занятия,
Assessment Plan Status,Статус плана оценки, Assessment Plan Status,Статус плана оценки,
Asset Depreciation Ledger,Износ Леджер активов, Asset Depreciation Ledger,Книга амортизации основных средств,
Asset Depreciations and Balances,Активов Амортизация и противовесов, Asset Depreciations and Balances,Активов Амортизация и противовесов,
Available Stock for Packing Items,Доступные Запасы для Комплектации Продуктов, Available Stock for Packing Items,Доступные Запасы для Комплектации Продуктов,
Bank Clearance Summary,Банк уплата по счетам итого, Bank Clearance Summary,Банк уплата по счетам итого,
@ -8559,7 +8561,7 @@ Sales Order Trends,Динамика по сделкам,
Sales Partner Commission Summary,Сводка комиссий партнеров по продажам, Sales Partner Commission Summary,Сводка комиссий партнеров по продажам,
Sales Partner Target Variance based on Item Group,Целевое отклонение партнера по продажам на основе группы товаров, Sales Partner Target Variance based on Item Group,Целевое отклонение партнера по продажам на основе группы товаров,
Sales Partner Transaction Summary,Сводка по сделкам с партнерами по продажам, Sales Partner Transaction Summary,Сводка по сделкам с партнерами по продажам,
Sales Partners Commission,Комиссионные Партнеров по продажам, Sales Partners Commission,Комиссия партнеров по продажам,
Invoiced Amount (Exclusive Tax),Сумма счета (без учета налога), Invoiced Amount (Exclusive Tax),Сумма счета (без учета налога),
Average Commission Rate,Средний уровень комиссии, Average Commission Rate,Средний уровень комиссии,
Sales Payment Summary,Сводка по продажам, Sales Payment Summary,Сводка по продажам,
@ -8579,7 +8581,7 @@ Student Fee Collection,Сбор студенческой платы,
Student Monthly Attendance Sheet,Ежемесячная посещаемость студентов, Student Monthly Attendance Sheet,Ежемесячная посещаемость студентов,
Subcontracted Item To Be Received,"Субподрядный предмет, подлежащий получению", Subcontracted Item To Be Received,"Субподрядный предмет, подлежащий получению",
Subcontracted Raw Materials To Be Transferred,Субподрядное сырье для передачи, Subcontracted Raw Materials To Be Transferred,Субподрядное сырье для передачи,
Supplier Ledger Summary,Список поставщиков, Supplier Ledger Summary,Сводка книги поставщиков,
Supplier-Wise Sales Analytics,Аналитика продаж в разрезе поставщиков, Supplier-Wise Sales Analytics,Аналитика продаж в разрезе поставщиков,
Support Hour Distribution,Распределение поддержки, Support Hour Distribution,Распределение поддержки,
TDS Computation Summary,Сводка расчетов TDS, TDS Computation Summary,Сводка расчетов TDS,
@ -9242,7 +9244,7 @@ Tasks Completed,Задачи выполнены,
Tasks Overdue,Просроченные задачи, Tasks Overdue,Просроченные задачи,
Completion,Завершение, Completion,Завершение,
Provident Fund Deductions,Отчисления в резервный фонд, Provident Fund Deductions,Отчисления в резервный фонд,
Purchase Order Analysis,Анализ заказа на закупку, Purchase Order Analysis,Анализ заказов на закупку,
From and To Dates are required.,Укажите даты от и до., From and To Dates are required.,Укажите даты от и до.,
To Date cannot be before From Date.,Дата не может быть раньше даты начала., To Date cannot be before From Date.,Дата не может быть раньше даты начала.,
Qty to Bill,Кол-во к счету, Qty to Bill,Кол-во к счету,
@ -9267,7 +9269,7 @@ Sales Order Analysis,Анализ заказов на продажу,
Amount Delivered,Сумма доставки, Amount Delivered,Сумма доставки,
Delay (in Days),Задержка (в днях), Delay (in Days),Задержка (в днях),
Group by Sales Order,Группировать по заказу на продажу, Group by Sales Order,Группировать по заказу на продажу,
Sales Value,Объем продаж, Sales Value, Объем продаж,
Stock Qty vs Serial No Count,Кол-во на складе по сравнению с серийным номером, Stock Qty vs Serial No Count,Кол-во на складе по сравнению с серийным номером,
Serial No Count,Серийный номер, Serial No Count,Серийный номер,
Work Order Summary,Сводка заказа на работу, Work Order Summary,Сводка заказа на работу,
@ -9320,8 +9322,8 @@ Error creating membership entry for {0},Ошибка создания запис
A customer is already linked to this Member,Клиент уже привязан к этому участнику, A customer is already linked to this Member,Клиент уже привязан к этому участнику,
End Date must not be lesser than Start Date,Дата окончания не должна быть меньше даты начала., End Date must not be lesser than Start Date,Дата окончания не должна быть меньше даты начала.,
Employee {0} already has Active Shift {1}: {2},Сотрудник {0} уже имеет активную смену {1}: {2}, Employee {0} already has Active Shift {1}: {2},Сотрудник {0} уже имеет активную смену {1}: {2},
from {0},от {0}, from {0}, от {0},
to {0},в {0}, to {0}, в {0},
Please select Employee first.,"Пожалуйста, сначала выберите сотрудника.", Please select Employee first.,"Пожалуйста, сначала выберите сотрудника.",
Please set {0} for the Employee or for Department: {1},Установите {0} для сотрудника или отдела: {1}, Please set {0} for the Employee or for Department: {1},Установите {0} для сотрудника или отдела: {1},
To Date should be greater than From Date,"Дата до должна быть больше, чем Дата", To Date should be greater than From Date,"Дата до должна быть больше, чем Дата",
@ -9838,3 +9840,8 @@ Enable European Access,Включить европейский доступ,
Creating Purchase Order ...,Создание заказа на поставку ..., Creating Purchase Order ...,Создание заказа на поставку ...,
"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Выберите поставщика из списка поставщиков по умолчанию для позиций ниже. При выборе Заказ на поставку будет сделан в отношении товаров, принадлежащих только выбранному Поставщику.", "Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Выберите поставщика из списка поставщиков по умолчанию для позиций ниже. При выборе Заказ на поставку будет сделан в отношении товаров, принадлежащих только выбранному Поставщику.",
Row #{}: You must select {} serial numbers for item {}.,Строка № {}: необходимо выбрать {} серийных номеров для позиции {}., Row #{}: You must select {} serial numbers for item {}.,Строка № {}: необходимо выбрать {} серийных номеров для позиции {}.,
Items & Pricing,Продукты и цены,
Overdue,Просрочено,
Completed,Завершенно,
Total Tasks,Всего задач,
Build,Конструктор,

Can't render this file because it is too large.

View File

@ -3,7 +3,6 @@ gocardless-pro~=1.22.0
googlemaps googlemaps
plaid-python~=7.2.1 plaid-python~=7.2.1
pycountry~=20.7.3 pycountry~=20.7.3
PyGithub~=1.55
python-stdnum~=1.16 python-stdnum~=1.16
python-youtube~=0.8.0 python-youtube~=0.8.0
taxjar~=1.9.2 taxjar~=1.9.2