Merge branch 'develop' into remove-nonprofit

This commit is contained in:
Chillar Anand 2022-02-04 10:17:25 +05:30 committed by GitHub
commit cfda849a8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 359 additions and 174 deletions

View File

@ -8,7 +8,10 @@ sudo apt-get install redis-server libcups2-dev
pip install frappe-bench pip install frappe-bench
git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 frappeuser=${FRAPPE_USER:-"frappe"}
frappebranch=${FRAPPE_BRANCH:-${GITHUB_BASE_REF:-${GITHUB_REF##*/}}}
git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
mkdir ~/frappe-bench/sites/test_site mkdir ~/frappe-bench/sites/test_site

View File

@ -6,12 +6,23 @@ on:
- '**.js' - '**.js'
- '**.md' - '**.md'
- '**.html' - '**.html'
workflow_dispatch:
push: push:
branches: [ develop ] branches: [ develop ]
paths-ignore: paths-ignore:
- '**.js' - '**.js'
- '**.md' - '**.md'
workflow_dispatch:
inputs:
user:
description: 'user'
required: true
default: 'frappe'
type: string
branch:
description: 'Branch name'
default: 'develop'
required: false
type: string
concurrency: concurrency:
group: server-mariadb-develop-${{ github.event.number }} group: server-mariadb-develop-${{ github.event.number }}
@ -95,6 +106,8 @@ jobs:
env: env:
DB: mariadb DB: mariadb
TYPE: server TYPE: server
FRAPPE_USER: ${{ github.event.inputs.user }}
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
- name: Run Tests - name: Run Tests
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage

View File

@ -441,7 +441,7 @@ erpnext.buying.get_items_from_product_bundle = function(frm) {
type: "GET", type: "GET",
method: "erpnext.stock.doctype.packed_item.packed_item.get_items_from_product_bundle", method: "erpnext.stock.doctype.packed_item.packed_item.get_items_from_product_bundle",
args: { args: {
args: { row: {
item_code: args.product_bundle, item_code: args.product_bundle,
quantity: args.quantity, quantity: args.quantity,
parenttype: frm.doc.doctype, parenttype: frm.doc.doctype,

View File

@ -1,7 +1,6 @@
import "./website_utils"; import "./website_utils";
import "./wishlist"; import "./wishlist";
import "./shopping_cart"; import "./shopping_cart";
import "./cart";
import "./customer_reviews"; import "./customer_reviews";
import "../../e_commerce/product_ui/list"; import "../../e_commerce/product_ui/list";
import "../../e_commerce/product_ui/views"; import "../../e_commerce/product_ui/views";

View File

@ -218,8 +218,6 @@
"label": "Conversion Factor" "label": "Conversion Factor"
}, },
{ {
"fetch_from": "item_code.valuation_rate",
"fetch_if_empty": 1,
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
@ -232,7 +230,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-09-01 15:10:29.646399", "modified": "2022-01-28 16:03:30.780111",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Packed Item", "name": "Packed Item",
@ -240,5 +238,6 @@
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@ -8,187 +8,250 @@ import json
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cstr, flt from frappe.utils import flt
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details, get_price_list_rate
class PackedItem(Document): class PackedItem(Document):
pass pass
def get_product_bundle_items(item_code):
return frappe.db.sql("""select t1.item_code, t1.qty, t1.uom, t1.description
from `tabProduct Bundle Item` t1, `tabProduct Bundle` t2
where t2.new_item_code=%s and t1.parent = t2.name order by t1.idx""", item_code, as_dict=1)
def get_packing_item_details(item, company):
return frappe.db.sql("""
select i.item_name, i.is_stock_item, i.description, i.stock_uom, id.default_warehouse
from `tabItem` i LEFT JOIN `tabItem Default` id ON id.parent=i.name and id.company=%s
where i.name = %s""",
(company, item), as_dict = 1)[0]
def get_bin_qty(item, warehouse):
det = frappe.db.sql("""select actual_qty, projected_qty from `tabBin`
where item_code = %s and warehouse = %s""", (item, warehouse), as_dict = 1)
return det and det[0] or frappe._dict()
def update_packing_list_item(doc, packing_item_code, qty, main_item_row, description):
if doc.amended_from:
old_packed_items_map = get_old_packed_item_details(doc.packed_items)
else:
old_packed_items_map = False
item = get_packing_item_details(packing_item_code, doc.company)
# check if exists
exists = 0
for d in doc.get("packed_items"):
if d.parent_item == main_item_row.item_code and d.item_code == packing_item_code:
if d.parent_detail_docname != main_item_row.name:
d.parent_detail_docname = main_item_row.name
pi, exists = d, 1
break
if not exists:
pi = doc.append('packed_items', {})
pi.parent_item = main_item_row.item_code
pi.item_code = packing_item_code
pi.item_name = item.item_name
pi.parent_detail_docname = main_item_row.name
pi.uom = item.stock_uom
pi.qty = flt(qty)
pi.conversion_factor = main_item_row.conversion_factor
if description and not pi.description:
pi.description = description
if not pi.warehouse and not doc.amended_from:
pi.warehouse = (main_item_row.warehouse if ((doc.get('is_pos') or item.is_stock_item \
or not item.default_warehouse) and main_item_row.warehouse) else item.default_warehouse)
if not pi.batch_no and not doc.amended_from:
pi.batch_no = cstr(main_item_row.get("batch_no"))
if not pi.target_warehouse:
pi.target_warehouse = main_item_row.get("target_warehouse")
bin = get_bin_qty(packing_item_code, pi.warehouse)
pi.actual_qty = flt(bin.get("actual_qty"))
pi.projected_qty = flt(bin.get("projected_qty"))
if old_packed_items_map and old_packed_items_map.get((packing_item_code, main_item_row.item_code)):
pi.batch_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].batch_no
pi.serial_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].serial_no
pi.warehouse = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].warehouse
def make_packing_list(doc): def make_packing_list(doc):
"""make packing list for Product Bundle item""" "Make/Update packing list for Product Bundle Item."
if doc.get("_action") and doc._action == "update_after_submit": return if doc.get("_action") and doc._action == "update_after_submit":
parent_items = []
for d in doc.get("items"):
if frappe.db.get_value("Product Bundle", {"new_item_code": d.item_code}):
for i in get_product_bundle_items(d.item_code):
update_packing_list_item(doc, i.item_code, flt(i.qty)*flt(d.stock_qty), d, i.description)
if [d.item_code, d.name] not in parent_items:
parent_items.append([d.item_code, d.name])
cleanup_packing_list(doc, parent_items)
if frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates"):
update_product_bundle_price(doc, parent_items)
def cleanup_packing_list(doc, parent_items):
"""Remove all those child items which are no longer present in main item table"""
delete_list = []
for d in doc.get("packed_items"):
if [d.parent_item, d.parent_detail_docname] not in parent_items:
# mark for deletion from doclist
delete_list.append(d)
if not delete_list:
return doc
packed_items = doc.get("packed_items")
doc.set("packed_items", [])
for d in packed_items:
if d not in delete_list:
add_item_to_packing_list(doc, d)
def add_item_to_packing_list(doc, packed_item):
doc.append("packed_items", {
'parent_item': packed_item.parent_item,
'item_code': packed_item.item_code,
'item_name': packed_item.item_name,
'uom': packed_item.uom,
'qty': packed_item.qty,
'rate': packed_item.rate,
'conversion_factor': packed_item.conversion_factor,
'description': packed_item.description,
'warehouse': packed_item.warehouse,
'batch_no': packed_item.batch_no,
'actual_batch_qty': packed_item.actual_batch_qty,
'serial_no': packed_item.serial_no,
'target_warehouse': packed_item.target_warehouse,
'actual_qty': packed_item.actual_qty,
'projected_qty': packed_item.projected_qty,
'incoming_rate': packed_item.incoming_rate,
'prevdoc_doctype': packed_item.prevdoc_doctype,
'parent_detail_docname': packed_item.parent_detail_docname
})
def update_product_bundle_price(doc, parent_items):
"""Updates the prices of Product Bundles based on the rates of the Items in the bundle."""
if not doc.get('items'):
return return
parent_items_index = 0 parent_items_price, reset = {}, False
bundle_price = 0 set_price_from_children = frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates")
for bundle_item in doc.get("packed_items"): stale_packed_items_table = get_indexed_packed_items_table(doc)
if parent_items[parent_items_index][0] == bundle_item.parent_item:
bundle_item_rate = bundle_item.rate if bundle_item.rate else 0 if not doc.is_new():
bundle_price += bundle_item.qty * bundle_item_rate reset = reset_packing_list_if_deleted_items_exist(doc)
for item_row in doc.get("items"):
if frappe.db.exists("Product Bundle", {"new_item_code": item_row.item_code}):
for bundle_item in get_product_bundle_items(item_row.item_code):
pi_row = add_packed_item_row(
doc=doc, packing_item=bundle_item,
main_item_row=item_row, packed_items_table=stale_packed_items_table,
reset=reset
)
item_data = get_packed_item_details(bundle_item.item_code, doc.company)
update_packed_item_basic_data(item_row, pi_row, bundle_item, item_data)
update_packed_item_stock_data(item_row, pi_row, bundle_item, item_data, doc)
update_packed_item_price_data(pi_row, item_data, doc)
update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
if set_price_from_children: # create/update bundle item wise price dict
update_product_bundle_rate(parent_items_price, pi_row)
if parent_items_price:
set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item
def get_indexed_packed_items_table(doc):
"""
Create dict from stale packed items table like:
{(Parent Item 1, Bundle Item 1, ae4b5678): {...}, (key): {value}}
Use: to quickly retrieve/check if row existed in table instead of looping n times
"""
indexed_table = {}
for packed_item in doc.get("packed_items"):
key = (packed_item.parent_item, packed_item.item_code, packed_item.parent_detail_docname)
indexed_table[key] = packed_item
return indexed_table
def reset_packing_list_if_deleted_items_exist(doc):
doc_before_save = doc.get_doc_before_save()
reset_table = False
if doc_before_save:
# reset table if:
# 1. items were deleted
# 2. if bundle item replaced by another item (same no. of items but different items)
# we maintain list to maintain repeated item rows as well
items_before_save = [item.item_code for item in doc_before_save.get("items")]
items_after_save = [item.item_code for item in doc.get("items")]
reset_table = items_before_save != items_after_save
else: else:
update_parent_item_price(doc, parent_items[parent_items_index][0], bundle_price) reset_table = True # reset if via Update Items (cannot determine action)
bundle_item_rate = bundle_item.rate if bundle_item.rate else 0 if reset_table:
bundle_price = bundle_item.qty * bundle_item_rate doc.set("packed_items", [])
parent_items_index += 1 return reset_table
# for the last product bundle def get_product_bundle_items(item_code):
if doc.get("packed_items"): product_bundle = frappe.qb.DocType("Product Bundle")
update_parent_item_price(doc, parent_items[parent_items_index][0], bundle_price) product_bundle_item = frappe.qb.DocType("Product Bundle Item")
def update_parent_item_price(doc, parent_item_code, bundle_price): query = (
parent_item_doc = doc.get('items', {'item_code': parent_item_code})[0] frappe.qb.from_(product_bundle_item)
.join(product_bundle).on(product_bundle_item.parent == product_bundle.name)
.select(
product_bundle_item.item_code,
product_bundle_item.qty,
product_bundle_item.uom,
product_bundle_item.description
).where(
product_bundle.new_item_code == item_code
).orderby(
product_bundle_item.idx
)
)
return query.run(as_dict=True)
current_parent_item_price = parent_item_doc.amount def add_packed_item_row(doc, packing_item, main_item_row, packed_items_table, reset):
if current_parent_item_price != bundle_price: """Add and return packed item row.
parent_item_doc.amount = bundle_price doc: Transaction document
update_parent_item_rate(parent_item_doc, bundle_price) packing_item (dict): Packed Item details
main_item_row (dict): Items table row corresponding to packed item
packed_items_table (dict): Packed Items table before save (indexed)
reset (bool): State if table is reset or preserved as is
"""
exists, pi_row = False, {}
def update_parent_item_rate(parent_item_doc, bundle_price): # check if row already exists in packed items table
parent_item_doc.rate = bundle_price/parent_item_doc.qty key = (main_item_row.item_code, packing_item.item_code, main_item_row.name)
if packed_items_table.get(key):
pi_row, exists = packed_items_table.get(key), True
@frappe.whitelist() if not exists:
def get_items_from_product_bundle(args): pi_row = doc.append('packed_items', {})
args = json.loads(args) elif reset: # add row if row exists but table is reset
items = [] pi_row.idx, pi_row.name = None, None
bundled_items = get_product_bundle_items(args["item_code"]) pi_row = doc.append('packed_items', pi_row)
for item in bundled_items:
args.update({ return pi_row
"item_code": item.item_code,
"qty": flt(args["quantity"]) * flt(item.qty) def get_packed_item_details(item_code, company):
item = frappe.qb.DocType("Item")
item_default = frappe.qb.DocType("Item Default")
query = (
frappe.qb.from_(item)
.left_join(item_default)
.on(
(item_default.parent == item.name)
& (item_default.company == company)
).select(
item.item_name, item.is_stock_item,
item.description, item.stock_uom,
item.valuation_rate,
item_default.default_warehouse
).where(
item.name == item_code
)
)
return query.run(as_dict=True)[0]
def update_packed_item_basic_data(main_item_row, pi_row, packing_item, item_data):
pi_row.parent_item = main_item_row.item_code
pi_row.parent_detail_docname = main_item_row.name
pi_row.item_code = packing_item.item_code
pi_row.item_name = item_data.item_name
pi_row.uom = item_data.stock_uom
pi_row.qty = flt(packing_item.qty) * flt(main_item_row.stock_qty)
pi_row.conversion_factor = main_item_row.conversion_factor
if not pi_row.description:
pi_row.description = packing_item.get("description")
def update_packed_item_stock_data(main_item_row, pi_row, packing_item, item_data, doc):
# TODO batch_no, actual_batch_qty, incoming_rate
if not pi_row.warehouse and not doc.amended_from:
fetch_warehouse = (doc.get('is_pos') or item_data.is_stock_item or not item_data.default_warehouse)
pi_row.warehouse = (main_item_row.warehouse if (fetch_warehouse and main_item_row.warehouse)
else item_data.default_warehouse)
if not pi_row.target_warehouse:
pi_row.target_warehouse = main_item_row.get("target_warehouse")
bin = get_packed_item_bin_qty(packing_item.item_code, pi_row.warehouse)
pi_row.actual_qty = flt(bin.get("actual_qty"))
pi_row.projected_qty = flt(bin.get("projected_qty"))
def update_packed_item_price_data(pi_row, item_data, doc):
"Set price as per price list or from the Item master."
if pi_row.rate:
return
item_doc = frappe.get_cached_doc("Item", pi_row.item_code)
row_data = pi_row.as_dict().copy()
row_data.update({
"company": doc.get("company"),
"price_list": doc.get("selling_price_list"),
"currency": doc.get("currency")
}) })
items.append(get_item_details(args)) rate = get_price_list_rate(row_data, item_doc).get("price_list_rate")
return items pi_row.rate = rate or item_data.get("valuation_rate") or 0.0
def update_packed_item_from_cancelled_doc(main_item_row, packing_item, pi_row, doc):
"Update packed item row details from cancelled doc into amended doc."
prev_doc_packed_items_map = None
if doc.amended_from:
prev_doc_packed_items_map = get_cancelled_doc_packed_item_details(doc.packed_items)
if prev_doc_packed_items_map and prev_doc_packed_items_map.get((packing_item.item_code, main_item_row.item_code)):
prev_doc_row = prev_doc_packed_items_map.get((packing_item.item_code, main_item_row.item_code))
pi_row.batch_no = prev_doc_row[0].batch_no
pi_row.serial_no = prev_doc_row[0].serial_no
pi_row.warehouse = prev_doc_row[0].warehouse
def get_packed_item_bin_qty(item, warehouse):
bin_data = frappe.db.get_values(
"Bin",
fieldname=["actual_qty", "projected_qty"],
filters={"item_code": item, "warehouse": warehouse},
as_dict=True
)
return bin_data[0] if bin_data else {}
def get_cancelled_doc_packed_item_details(old_packed_items):
prev_doc_packed_items_map = {}
for items in old_packed_items:
prev_doc_packed_items_map.setdefault((items.item_code ,items.parent_item), []).append(items.as_dict())
return prev_doc_packed_items_map
def update_product_bundle_rate(parent_items_price, pi_row):
"""
Update the price dict of Product Bundles based on the rates of the Items in the bundle.
Stucture:
{(Bundle Item 1, ae56fgji): 150.0, (Bundle Item 2, bc78fkjo): 200.0}
"""
key = (pi_row.parent_item, pi_row.parent_detail_docname)
rate = parent_items_price.get(key)
if not rate:
parent_items_price[key] = 0.0
parent_items_price[key] += flt(pi_row.rate)
def set_product_bundle_rate_amount(doc, parent_items_price):
"Set cumulative rate and amount in bundle item."
for item in doc.get("items"):
bundle_rate = parent_items_price.get((item.item_code, item.name))
if bundle_rate and bundle_rate != item.rate:
item.rate = bundle_rate
item.amount = flt(bundle_rate * item.qty)
def on_doctype_update(): def on_doctype_update():
frappe.db.add_index("Packed Item", ["item_code", "warehouse"]) frappe.db.add_index("Packed Item", ["item_code", "warehouse"])
def get_old_packed_item_details(old_packed_items):
old_packed_items_map = {} @frappe.whitelist()
for items in old_packed_items: def get_items_from_product_bundle(row):
old_packed_items_map.setdefault((items.item_code ,items.parent_item), []).append(items.as_dict()) row, items = json.loads(row), []
return old_packed_items_map
bundled_items = get_product_bundle_items(row["item_code"])
for item in bundled_items:
row.update({
"item_code": item.item_code,
"qty": flt(row["quantity"]) * flt(item.qty)
})
items.append(get_item_details(row))
return items

View File

@ -0,0 +1,104 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.tests.utils import ERPNextTestCase, change_settings
class TestPackedItem(ERPNextTestCase):
"Test impact on Packed Items table in various scenarios."
@classmethod
def setUpClass(cls) -> None:
make_item("_Test Product Bundle X", {"is_stock_item": 0})
make_item("_Test Bundle Item 1", {"is_stock_item": 1})
make_item("_Test Bundle Item 2", {"is_stock_item": 1})
make_item("_Test Normal Stock Item", {"is_stock_item": 1})
make_product_bundle(
"_Test Product Bundle X",
["_Test Bundle Item 1", "_Test Bundle Item 2"],
qty=2
)
def test_sales_order_adding_bundle_item(self):
"Test impact on packed items if bundle item row is added."
so = make_sales_order(item_code = "_Test Product Bundle X", qty=1,
do_not_submit=True)
self.assertEqual(so.items[0].qty, 1)
self.assertEqual(len(so.packed_items), 2)
self.assertEqual(so.packed_items[0].item_code, "_Test Bundle Item 1")
self.assertEqual(so.packed_items[0].qty, 2)
def test_sales_order_updating_bundle_item(self):
"Test impact on packed items if bundle item row is updated."
so = make_sales_order(item_code = "_Test Product Bundle X", qty=1,
do_not_submit=True)
so.items[0].qty = 2 # change qty
so.save()
self.assertEqual(so.packed_items[0].qty, 4)
self.assertEqual(so.packed_items[1].qty, 4)
# change item code to non bundle item
so.items[0].item_code = "_Test Normal Stock Item"
so.save()
self.assertEqual(len(so.packed_items), 0)
def test_sales_order_recurring_bundle_item(self):
"Test impact on packed items if same bundle item is added and removed."
so_items = []
for qty in [2, 4, 6, 8]:
so_items.append({
"item_code": "_Test Product Bundle X",
"qty": qty,
"rate": 400,
"warehouse": "_Test Warehouse - _TC"
})
# create SO with recurring bundle item
so = make_sales_order(item_list=so_items, do_not_submit=True)
# check alternate rows for qty
self.assertEqual(len(so.packed_items), 8)
self.assertEqual(so.packed_items[1].item_code, "_Test Bundle Item 2")
self.assertEqual(so.packed_items[1].qty, 4)
self.assertEqual(so.packed_items[3].qty, 8)
self.assertEqual(so.packed_items[5].qty, 12)
self.assertEqual(so.packed_items[7].qty, 16)
# delete intermediate row (2nd)
del so.items[1]
so.save()
# check alternate rows for qty
self.assertEqual(len(so.packed_items), 6)
self.assertEqual(so.packed_items[1].qty, 4)
self.assertEqual(so.packed_items[3].qty, 12)
self.assertEqual(so.packed_items[5].qty, 16)
# delete last row
del so.items[2]
so.save()
# check alternate rows for qty
self.assertEqual(len(so.packed_items), 4)
self.assertEqual(so.packed_items[1].qty, 4)
self.assertEqual(so.packed_items[3].qty, 12)
@change_settings("Selling Settings", {"editable_bundle_item_rates": 1})
def test_sales_order_bundle_item_cumulative_price(self):
"Test if Bundle Item rate is cumulative from packed items."
so = make_sales_order(item_code = "_Test Product Bundle X", qty=2,
do_not_submit=True)
so.packed_items[0].rate = 150
so.packed_items[1].rate = 200
so.save()
self.assertEqual(so.items[0].rate, 350)
self.assertEqual(so.items[0].amount, 700)

View File

@ -104,6 +104,7 @@ def get_columns():
{"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"}, {"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"},
{"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"}, {"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"},
{"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency"}, {"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency"},
{"label": _("Value Change"), "fieldname": "stock_value_difference", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency"},
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110}, {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100}, {"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
{"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100}, {"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},

View File

@ -1,9 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
// js inside blog page // JS exclusive to /cart page
// shopping cart
frappe.provide("erpnext.e_commerce.shopping_cart"); frappe.provide("erpnext.e_commerce.shopping_cart");
var shopping_cart = erpnext.e_commerce.shopping_cart; var shopping_cart = erpnext.e_commerce.shopping_cart;

View File

@ -783,6 +783,7 @@ Default Activity Cost exists for Activity Type - {0},Es gibt Standard-Aktivität
Default BOM ({0}) must be active for this item or its template,Standardstückliste ({0}) muss für diesen Artikel oder dessen Vorlage aktiv sein, Default BOM ({0}) must be active for this item or its template,Standardstückliste ({0}) muss für diesen Artikel oder dessen Vorlage aktiv sein,
Default BOM for {0} not found,Standardstückliste für {0} nicht gefunden, Default BOM for {0} not found,Standardstückliste für {0} nicht gefunden,
Default BOM not found for Item {0} and Project {1},Standard-Stückliste nicht gefunden für Position {0} und Projekt {1}, Default BOM not found for Item {0} and Project {1},Standard-Stückliste nicht gefunden für Position {0} und Projekt {1},
Default In-Transit Warehouse, Standardlager für Waren im Transit,
Default Letter Head,Standardbriefkopf, Default Letter Head,Standardbriefkopf,
Default Tax Template,Standardsteuervorlage, Default Tax Template,Standardsteuervorlage,
Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,"Die Standard-Maßeinheit für Artikel {0} kann nicht direkt geändert werden, weil Sie bereits einige Transaktionen mit einer anderen Maßeinheit durchgeführt haben. Sie müssen einen neuen Artikel erstellen, um eine andere Standard-Maßeinheit verwenden zukönnen.", Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,"Die Standard-Maßeinheit für Artikel {0} kann nicht direkt geändert werden, weil Sie bereits einige Transaktionen mit einer anderen Maßeinheit durchgeführt haben. Sie müssen einen neuen Artikel erstellen, um eine andere Standard-Maßeinheit verwenden zukönnen.",
@ -1054,6 +1055,7 @@ Fiscal Year {0} is required,Fiscal Year {0} ist erforderlich,
Fiscal Year {0} not found,Das Geschäftsjahr {0} nicht gefunden, Fiscal Year {0} not found,Das Geschäftsjahr {0} nicht gefunden,
Fixed Asset,Anlagevermögen, Fixed Asset,Anlagevermögen,
Fixed Asset Item must be a non-stock item.,Posten des Anlagevermögens muss ein Nichtlagerposition sein., Fixed Asset Item must be a non-stock item.,Posten des Anlagevermögens muss ein Nichtlagerposition sein.,
Fixed Asset Defaults, Standards für Anlagevermögen,
Fixed Assets,Anlagevermögen, Fixed Assets,Anlagevermögen,
Following Material Requests have been raised automatically based on Item's re-order level,Folgende Materialanfragen wurden automatisch auf der Grundlage der Nachbestellmenge des Artikels generiert, Following Material Requests have been raised automatically based on Item's re-order level,Folgende Materialanfragen wurden automatisch auf der Grundlage der Nachbestellmenge des Artikels generiert,
Following accounts might be selected in GST Settings:,In den GST-Einstellungen können folgende Konten ausgewählt werden:, Following accounts might be selected in GST Settings:,In den GST-Einstellungen können folgende Konten ausgewählt werden:,
@ -2352,6 +2354,7 @@ Removed items with no change in quantity or value.,Artikel wurden ohne Veränder
Reopen,Wieder öffnen, Reopen,Wieder öffnen,
Reorder Level,Meldebestand, Reorder Level,Meldebestand,
Reorder Qty,Nachbestellmenge, Reorder Qty,Nachbestellmenge,
Repair and Maintenance Account, Konto für Reparatur/Instandhaltung von Anlagen und Maschinen,
Repeat Customer Revenue,Umsatz Bestandskunden, Repeat Customer Revenue,Umsatz Bestandskunden,
Repeat Customers,Bestandskunden, Repeat Customers,Bestandskunden,
Replace BOM and update latest price in all BOMs,Ersetzen Sie die Stückliste und aktualisieren Sie den aktuellen Preis in allen Stücklisten, Replace BOM and update latest price in all BOMs,Ersetzen Sie die Stückliste und aktualisieren Sie den aktuellen Preis in allen Stücklisten,
@ -3796,7 +3799,7 @@ Intermediate,Mittlere,
Invalid Barcode. There is no Item attached to this barcode.,Ungültiger Barcode. Es ist kein Artikel an diesen Barcode angehängt., Invalid Barcode. There is no Item attached to this barcode.,Ungültiger Barcode. Es ist kein Artikel an diesen Barcode angehängt.,
Invalid credentials,Ungültige Anmeldeinformationen, Invalid credentials,Ungültige Anmeldeinformationen,
Invite as User,Als Benutzer einladen, Invite as User,Als Benutzer einladen,
Issue Priority.,Ausgabepriorität., Issue Priority.,Anfragepriorität.,
Issue Type.,Problemtyp., Issue Type.,Problemtyp.,
"It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.","Es scheint, dass ein Problem mit der Stripe-Konfiguration des Servers vorliegt. Im Falle eines Fehlers wird der Betrag Ihrem Konto gutgeschrieben.", "It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.","Es scheint, dass ein Problem mit der Stripe-Konfiguration des Servers vorliegt. Im Falle eines Fehlers wird der Betrag Ihrem Konto gutgeschrieben.",
Item Reported,Gegenstand gemeldet, Item Reported,Gegenstand gemeldet,
@ -4857,6 +4860,7 @@ Payment Entry Reference,Zahlungsreferenz,
Allocated,Zugewiesen, Allocated,Zugewiesen,
Payment Gateway Account,Payment Gateway Konto, Payment Gateway Account,Payment Gateway Konto,
Payment Account,Zahlungskonto, Payment Account,Zahlungskonto,
Default Payment Discount Account, Standard Rabattkonto für Zahlungen,
Default Payment Request Message,Standard Payment Request Message, Default Payment Request Message,Standard Payment Request Message,
PMO-,PMO-, PMO-,PMO-,
Payment Order Type,Zahlungsauftragsart, Payment Order Type,Zahlungsauftragsart,
@ -7789,7 +7793,8 @@ Round Off Cost Center,Abschluss-Kostenstelle,
Discount Allowed Account,Rabatt erlaubtes Konto, Discount Allowed Account,Rabatt erlaubtes Konto,
Discount Received Account,Discount Received Account, Discount Received Account,Discount Received Account,
Exchange Gain / Loss Account,Konto für Wechselkursdifferenzen, Exchange Gain / Loss Account,Konto für Wechselkursdifferenzen,
Unrealized Exchange Gain/Loss Account,Konto für unrealisierte Wechselkurs-Gewinne / -Verluste, Unrealized Exchange Gain/Loss Account,Konto für nicht realisierte Wechselkurs-Gewinne/ -Verluste,
Unrealized Profit / Loss Account, Konto für nicht realisierten Gewinn/Verlust,
Allow Account Creation Against Child Company,Kontoerstellung für untergeordnete Unternehmen zulassen, Allow Account Creation Against Child Company,Kontoerstellung für untergeordnete Unternehmen zulassen,
Default Payable Account,Standard-Verbindlichkeitenkonto, Default Payable Account,Standard-Verbindlichkeitenkonto,
Default Employee Advance Account,Standardkonto für Vorschüsse an Arbeitnehmer, Default Employee Advance Account,Standardkonto für Vorschüsse an Arbeitnehmer,

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