Merge branch 'develop' into gst-category

This commit is contained in:
Afshan 2021-10-27 14:13:26 +05:30 committed by GitHub
commit f0bdf9426b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 313 additions and 69 deletions

View File

@ -55,14 +55,6 @@ The Easy Way: our install script for bench will install all dependencies (e.g. M
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
### Virtual Image
You can download a virtual image to run ERPNext in a virtual machine on your local system.
- [ERPNext Download](http://erpnext.com/download)
System and user credentials are listed on the download page.
---
## License

View File

@ -10,6 +10,7 @@ from frappe.contacts.doctype.address.address import (
class ERPNextAddress(Address):
def validate(self):
self.validate_reference()
self.update_compnay_address()
super(ERPNextAddress, self).validate()
def link_address(self):
@ -19,6 +20,11 @@ class ERPNextAddress(Address):
return super(ERPNextAddress, self).link_address()
def update_compnay_address(self):
for link in self.get('links'):
if link.link_doctype == 'Company':
self.is_your_company_address = 1
def validate_reference(self):
if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company"

View File

@ -27,10 +27,12 @@
"payment_accounts_section",
"party_balance",
"paid_from",
"paid_from_account_type",
"paid_from_account_currency",
"paid_from_account_balance",
"column_break_18",
"paid_to",
"paid_to_account_type",
"paid_to_account_currency",
"paid_to_account_balance",
"payment_amounts_section",
@ -440,7 +442,8 @@
"depends_on": "eval:(doc.paid_from && doc.paid_to)",
"fieldname": "reference_no",
"fieldtype": "Data",
"label": "Cheque/Reference No"
"label": "Cheque/Reference No",
"mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')"
},
{
"fieldname": "column_break_23",
@ -452,6 +455,7 @@
"fieldname": "reference_date",
"fieldtype": "Date",
"label": "Cheque/Reference Date",
"mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')",
"search_index": 1
},
{
@ -707,15 +711,30 @@
"label": "Received Amount After Tax (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fetch_from": "paid_from.account_type",
"fieldname": "paid_from_account_type",
"fieldtype": "Data",
"hidden": 1,
"label": "Paid From Account Type"
},
{
"fetch_from": "paid_to.account_type",
"fieldname": "paid_to_account_type",
"fieldtype": "Data",
"hidden": 1,
"label": "Paid To Account Type"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-07-09 08:58:15.008761",
"modified": "2021-10-22 17:50:24.632806",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{

View File

@ -29,6 +29,9 @@ def get_pricing_rules(args, doc=None):
pricing_rules = []
values = {}
if not frappe.db.exists('Pricing Rule', {'disable': 0, args.transaction_type: 1}):
return
for apply_on in ['Item Code', 'Item Group', 'Brand']:
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):

View File

@ -590,5 +590,11 @@ frappe.ui.form.on("Purchase Invoice", {
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
if (frm.doc.company) {
frappe.db.get_value('Company', frm.doc.company, 'default_payable_account', (r) => {
frm.set_value('credit_to', r.default_payable_account);
});
}
},
})

View File

@ -12,6 +12,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
}
company() {
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
let me = this;
if (this.frm.doc.company) {
frappe.db.get_value('Company', this.frm.doc.company, 'default_receivable_account', (r) => {
me.frm.set_value('debit_to', r.default_receivable_account);
});
}
}
onload() {
var me = this;

View File

@ -1975,22 +1975,23 @@ def update_multi_mode_option(doc, pos_profile):
def append_payment(payment_mode):
payment = doc.append('payments', {})
payment.default = payment_mode.default
payment.mode_of_payment = payment_mode.parent
payment.mode_of_payment = payment_mode.mop
payment.account = payment_mode.default_account
payment.type = payment_mode.type
doc.set('payments', [])
invalid_modes = []
for pos_payment_method in pos_profile.get('payments'):
pos_payment_method = pos_payment_method.as_dict()
mode_of_payments = [d.mode_of_payment for d in pos_profile.get('payments')]
mode_of_payments_info = get_mode_of_payments_info(mode_of_payments, doc.company)
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
for row in pos_profile.get('payments'):
payment_mode = mode_of_payments_info.get(row.mode_of_payment)
if not payment_mode:
invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment))
invalid_modes.append(get_link_to_form("Mode of Payment", row.mode_of_payment))
continue
payment_mode[0].default = pos_payment_method.default
append_payment(payment_mode[0])
payment_mode.default = row.default
append_payment(payment_mode)
if invalid_modes:
if invalid_modes == 1:
@ -2006,6 +2007,24 @@ def get_all_mode_of_payments(doc):
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
{'company': doc.company}, as_dict=1)
def get_mode_of_payments_info(mode_of_payments, company):
data = frappe.db.sql(
"""
select
mpa.default_account, mpa.parent as mop, mp.type as type
from
`tabMode of Payment Account` mpa,`tabMode of Payment` mp
where
mpa.parent = mp.name and
mpa.company = %s and
mp.enabled = 1 and
mp.name in (%s)
group by
mp.name
""", (company, mode_of_payments), as_dict=1)
return {row.get('mop'): row for row in data}
def get_mode_of_payment_info(mode_of_payment, company):
return frappe.db.sql("""
select mpa.default_account, mpa.parent, mp.type as type

View File

@ -8,7 +8,8 @@ frappe.ui.form.on('Tax Withholding Category', {
if (child.company) {
return {
filters: {
'company': child.company
'company': child.company,
'root_type': ['in', ['Asset', 'Liability']]
}
};
}

View File

@ -13,6 +13,7 @@ from frappe.utils import cint, getdate
class TaxWithholdingCategory(Document):
def validate(self):
self.validate_dates()
self.validate_accounts()
self.validate_thresholds()
def validate_dates(self):
@ -25,6 +26,14 @@ class TaxWithholdingCategory(Document):
if last_date and getdate(d.to_date) < getdate(last_date):
frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx))
def validate_accounts(self):
existing_accounts = []
for d in self.get('accounts'):
if d.get('account') in existing_accounts:
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get('account'))))
existing_accounts.append(d.get('account'))
def validate_thresholds(self):
for d in self.get('rates'):
if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold:

View File

@ -25,7 +25,6 @@
"column_break0",
"supplier_group",
"supplier_type",
"pan",
"allow_purchase_invoice_creation_without_purchase_order",
"allow_purchase_invoice_creation_without_purchase_receipt",
"disabled",
@ -176,11 +175,6 @@
"options": "Company\nIndividual",
"reqd": 1
},
{
"fieldname": "pan",
"fieldtype": "Data",
"label": "PAN"
},
{
"fieldname": "language",
"fieldtype": "Link",
@ -438,11 +432,12 @@
"link_fieldname": "party"
}
],
"modified": "2021-09-06 17:37:56.522233",
"modified": "2021-10-20 22:03:33.147249",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{

View File

@ -1032,7 +1032,7 @@ class AccountsController(TransactionBase):
if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
.format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange")
.format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True)
def throw_overbill_exception(self, item, max_allowed_amt):
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
@ -1354,8 +1354,8 @@ class AccountsController(TransactionBase):
total = 0
base_total = 0
for d in self.get("payment_schedule"):
total += flt(d.payment_amount)
base_total += flt(d.base_payment_amount)
total += flt(d.payment_amount, d.precision("payment_amount"))
base_total += flt(d.base_payment_amount, d.precision("base_payment_amount"))
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
grand_total = self.get("rounded_total") or self.grand_total
@ -1371,8 +1371,9 @@ class AccountsController(TransactionBase):
else:
grand_total -= self.get("total_advance")
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
if total != flt(grand_total, self.precision("grand_total")) or \
base_total != flt(base_grand_total, self.precision("base_grand_total")):
if flt(total, self.precision("grand_total")) != flt(grand_total, self.precision("grand_total")) or \
flt(base_total, self.precision("base_grand_total")) != flt(base_grand_total, self.precision("base_grand_total")):
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
def is_rounded_total_disabled(self):

View File

@ -216,11 +216,14 @@ class StatusUpdater(Document):
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
item[args['target_ref_field']]) * 100
if overflow_percent - allowance > 0.01 and role not in frappe.get_roles():
if overflow_percent - allowance > 0.01:
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
item['reduce_by'] = item[args['target_field']] - item['max_allowed']
self.limits_crossed_error(args, item, qty_or_amount)
if role not in frappe.get_roles():
self.limits_crossed_error(args, item, qty_or_amount)
else:
self.warn_about_bypassing_with_role(item, qty_or_amount, role)
def limits_crossed_error(self, args, item, qty_or_amount):
'''Raise exception for limits crossed'''
@ -238,6 +241,19 @@ class StatusUpdater(Document):
frappe.bold(item.get('item_code'))
) + '<br><br>' + action_msg, OverAllowanceError, title = _('Limit Crossed'))
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
msg = (_("{} of {} {} ignored for item {} because you have {} role.")
.format(
action,
_(item["target_ref_field"].title()),
frappe.bold(item["reduce_by"]),
frappe.bold(item.get('item_code')),
role)
)
frappe.msgprint(msg, indicator="orange", alert=True)
def update_qty(self, update_modified=True):
"""Updates qty or amount at row level

View File

@ -156,6 +156,8 @@ def get_employees_having_an_event_today(event_type):
DAY({condition_column}) = DAY(%(today)s)
AND
MONTH({condition_column}) = MONTH(%(today)s)
AND
YEAR({condition_column}) < YEAR(%(today)s)
AND
`status` = 'Active'
""",
@ -166,6 +168,8 @@ def get_employees_having_an_event_today(event_type):
DATE_PART('day', {condition_column}) = date_part('day', %(today)s)
AND
DATE_PART('month', {condition_column}) = date_part('month', %(today)s)
AND
DATE_PART('year', {condition_column}) < date_part('year', %(today)s)
AND
"status" = 'Active'
""",

View File

@ -100,7 +100,7 @@
],
"istable": 1,
"links": [],
"modified": "2020-09-23 20:27:36.027728",
"modified": "2021-10-26 20:27:36.027728",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Taxes and Charges",

View File

@ -311,7 +311,7 @@ class ProductionPlan(Document):
if self.total_produced_qty > 0:
self.status = "In Process"
if self.total_produced_qty >= self.total_planned_qty:
if self.check_have_work_orders_completed():
self.status = "Completed"
if self.status != 'Completed':
@ -575,6 +575,15 @@ class ProductionPlan(Document):
self.append("sub_assembly_items", data)
def check_have_work_orders_completed(self):
wo_status = frappe.db.get_list(
"Work Order",
filters={"production_plan": self.name},
fields="status",
pluck="status"
)
return all(s == "Completed" for s in wo_status)
@frappe.whitelist()
def download_raw_materials(doc, warehouses=None):
if isinstance(doc, str):

View File

@ -308,5 +308,7 @@ erpnext.patches.v13_0.set_status_in_maintenance_schedule_table
erpnext.patches.v13_0.add_default_interview_notification_templates
erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting
erpnext.patches.v13_0.requeue_failed_reposts
erpnext.patches.v12_0.update_production_plan_status
erpnext.patches.v13_0.healthcare_deprecation_warning
erpnext.patches.v14_0.delete_healthcare_doctypes
erpnext.patches.v13_0.create_pan_field_for_india #2

View File

@ -0,0 +1,31 @@
# Copyright (c) 2021, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
def execute():
frappe.reload_doc("manufacturing", "doctype", "production_plan")
frappe.db.sql("""
UPDATE `tabProduction Plan` ppl
SET status = "Completed"
WHERE ppl.name IN (
SELECT ss.name FROM (
SELECT
(
count(wo.status = "Completed") =
count(pp.name)
) =
(
pp.status != "Completed"
AND pp.total_produced_qty >= pp.total_planned_qty
) AS should_set,
pp.name AS name
FROM
`tabWork Order` wo INNER JOIN`tabProduction Plan` pp
ON wo.production_plan = pp.name
GROUP BY pp.name
HAVING should_set = 1
) ss
)
""")

View File

@ -0,0 +1,28 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
frappe.reload_doc('buying', 'doctype', 'supplier', force=True)
frappe.reload_doc('selling', 'doctype', 'customer', force=True)
custom_fields = {
'Supplier': [
{
'fieldname': 'pan',
'label': 'PAN',
'fieldtype': 'Data',
'insert_after': 'supplier_type'
}
],
'Customer': [
{
'fieldname': 'pan',
'label': 'PAN',
'fieldtype': 'Data',
'insert_after': 'customer_type'
}
]
}
create_custom_fields(custom_fields, update=True)

View File

@ -614,11 +614,17 @@ def get_custom_fields():
fieldtype='Currency', insert_after='monthly_hra_exemption', read_only=1, depends_on='house_rent_payment_amount')
],
'Supplier': [
{
'fieldname': 'pan',
'label': 'PAN',
'fieldtype': 'Data',
'insert_after': 'supplier_type'
},
{
'fieldname': 'gst_transporter_id',
'label': 'GST Transporter ID',
'fieldtype': 'Data',
'insert_after': 'supplier_type',
'insert_after': 'pan',
'depends_on': 'eval:doc.is_transporter'
},
{
@ -640,11 +646,17 @@ def get_custom_fields():
}
],
'Customer': [
{
'fieldname': 'pan',
'label': 'PAN',
'fieldtype': 'Data',
'insert_after': 'customer_type'
},
{
'fieldname': 'gst_category',
'label': 'GST Category',
'fieldtype': 'Select',
'insert_after': 'customer_type',
'insert_after': 'pan',
'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
'default': 'Unregistered'
},

View File

@ -63,7 +63,7 @@ def validate_gstin_for_india(doc, method):
.format(doc.gst_state_number), title=_("Invalid GSTIN"))
def validate_pan_for_india(doc, method):
if doc.get('country') != 'India' or not doc.pan:
if doc.get('country') != 'India' or not doc.get('pan'):
return
if not PAN_NUMBER_FORMAT.match(doc.pan):

View File

@ -16,7 +16,6 @@
"customer_name",
"gender",
"customer_type",
"pan",
"tax_withholding_category",
"default_bank_account",
"lead_name",
@ -486,11 +485,6 @@
"fieldtype": "Check",
"label": "Allow Sales Invoice Creation Without Delivery Note"
},
{
"fieldname": "pan",
"fieldtype": "Data",
"label": "PAN"
},
{
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
@ -517,11 +511,12 @@
"link_fieldname": "party"
}
],
"modified": "2021-09-06 17:38:54.196663",
"modified": "2021-10-20 22:07:52.485809",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{

View File

@ -1,6 +1,6 @@
{
"charts": [],
"content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Projects Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"HR Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Selling Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Buying Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Support Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Shopping Cart Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Portal Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Manufacturing Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Education Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Hotel Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Domain Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Products Settings\", \"col\": 4}}]",
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":4}}]",
"creation": "2020-03-12 14:47:51.166455",
"docstatus": 0,
"doctype": "Workspace",
@ -10,7 +10,7 @@
"idx": 0,
"label": "ERPNext Settings",
"links": [],
"modified": "2021-08-05 12:15:59.052328",
"modified": "2021-10-26 21:32:55.323591",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",
@ -27,6 +27,14 @@
"link_to": "Projects Settings",
"type": "DocType"
},
{
"color": "Grey",
"doc_view": "",
"icon": "dot-horizontal",
"label": "Naming Series",
"link_to": "Naming Series",
"type": "DocType"
},
{
"icon": "accounting",
"label": "Accounts Settings",

View File

@ -18,7 +18,9 @@
"get_item_locations",
"section_break_6",
"locations",
"amended_from"
"amended_from",
"print_settings_section",
"group_same_items"
],
"fields": [
{
@ -110,14 +112,28 @@
"options": "STO-PICK-.YYYY.-",
"reqd": 1,
"set_only_once": 1
},
{
"fieldname": "print_settings_section",
"fieldtype": "Section Break",
"label": "Print Settings"
},
{
"allow_on_submit": 1,
"default": "0",
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group Same Items",
"print_hide": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-03-17 11:38:41.932875",
"modified": "2021-10-05 15:08:40.369957",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@ -184,4 +200,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@ -2,10 +2,8 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import json
from collections import OrderedDict
from collections import OrderedDict, defaultdict
import frappe
from frappe import _
@ -121,6 +119,34 @@ class PickList(Document):
and (self.for_qty is None or self.for_qty == 0):
frappe.throw(_("Qty of Finished Goods Item should be greater than 0."))
def before_print(self, settings=None):
if self.get("group_same_items"):
self.group_similar_items()
def group_similar_items(self):
group_item_qty = defaultdict(float)
group_picked_qty = defaultdict(float)
for item in self.locations:
group_item_qty[(item.item_code, item.warehouse)] += item.qty
group_picked_qty[(item.item_code, item.warehouse)] += item.picked_qty
duplicate_list = []
for item in self.locations:
if (item.item_code, item.warehouse) in group_item_qty:
item.qty = group_item_qty[(item.item_code, item.warehouse)]
item.picked_qty = group_picked_qty[(item.item_code, item.warehouse)]
item.stock_qty = group_item_qty[(item.item_code, item.warehouse)]
del group_item_qty[(item.item_code, item.warehouse)]
else:
duplicate_list.append(item)
for item in duplicate_list:
self.remove(item)
for idx, item in enumerate(self.locations, start=1):
item.idx = idx
def validate_item_locations(pick_list):
if not pick_list.locations:

View File

@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _dict
test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
@ -356,6 +357,39 @@ class TestPickList(ERPNextTestCase):
sales_order.cancel()
purchase_receipt.cancel()
def test_pick_list_grouping_before_print(self):
def _compare_dicts(a, b):
"compare dicts but ignore missing keys in `a`"
for key, value in a.items():
self.assertEqual(b.get(key), value, msg=f"{key} doesn't match")
# nothing should be grouped
pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[
_dict(item_code="A", warehouse="X", qty=1, picked_qty=2),
_dict(item_code="B", warehouse="X", qty=1, picked_qty=2),
_dict(item_code="A", warehouse="Y", qty=1, picked_qty=2),
_dict(item_code="B", warehouse="Y", qty=1, picked_qty=2),
])
pl.before_print()
self.assertEqual(len(pl.locations), 4)
# grouping should halve the number of items
pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[
_dict(item_code="A", warehouse="X", qty=5, picked_qty=1),
_dict(item_code="B", warehouse="Y", qty=4, picked_qty=2),
_dict(item_code="A", warehouse="X", qty=3, picked_qty=2),
_dict(item_code="B", warehouse="Y", qty=2, picked_qty=2),
])
pl.before_print()
self.assertEqual(len(pl.locations), 2)
expected_items = [
_dict(item_code="A", warehouse="X", qty=8, picked_qty=3),
_dict(item_code="B", warehouse="Y", qty=6, picked_qty=4),
]
for expected_item, created_item in zip(expected_items, pl.locations):
_compare_dicts(expected_item, created_item)
# def test_pick_list_skips_items_in_expired_batch(self):
# pass

View File

@ -89,7 +89,13 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
elif out.get("warehouse"):
out.update(get_bin_details(args.item_code, out.warehouse, args.company))
if doc and doc.get('doctype') == 'Purchase Order':
# calculate company_total_stock only for po
bin_details = get_bin_details(args.item_code, out.warehouse, args.company)
else:
bin_details = get_bin_details(args.item_code, out.warehouse)
out.update(bin_details)
# update args with out, if key or value not exists
for key, value in iteritems(out):
@ -485,8 +491,9 @@ def get_item_tax_template(args, item, out):
"item_tax_template": None
}
"""
item_tax_template = args.get("item_tax_template")
item_tax_template = _get_item_tax_template(args, item.taxes, out)
item_tax_template = None
if item.taxes:
item_tax_template = _get_item_tax_template(args, item.taxes, out)
if not item_tax_template:
item_group = item.item_group
@ -502,17 +509,17 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
taxes_with_no_validity = []
for tax in taxes:
tax_company = frappe.get_value("Item Tax Template", tax.item_tax_template, 'company')
if (tax.valid_from or tax.maximum_net_rate) and tax_company == args['company']:
# In purchase Invoice first preference will be given to supplier invoice date
# if supplier date is not present then posting date
validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date')
tax_company = frappe.get_cached_value("Item Tax Template", tax.item_tax_template, 'company')
if tax_company == args['company']:
if (tax.valid_from or tax.maximum_net_rate):
# In purchase Invoice first preference will be given to supplier invoice date
# if supplier date is not present then posting date
validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date')
if getdate(tax.valid_from) <= getdate(validation_date) \
and is_within_valid_range(args, tax):
taxes_with_validity.append(tax)
else:
if tax_company == args['company']:
if getdate(tax.valid_from) <= getdate(validation_date) \
and is_within_valid_range(args, tax):
taxes_with_validity.append(tax)
else:
taxes_with_no_validity.append(tax)
if taxes_with_validity:
@ -890,8 +897,7 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa
res[fieldname] = pos_profile.get(fieldname)
if res.get("warehouse"):
res.actual_qty = get_bin_details(args.item_code,
res.warehouse).get("actual_qty")
res.actual_qty = get_bin_details(args.item_code, res.warehouse).get("actual_qty")
return res

View File

@ -123,12 +123,11 @@ def set_as_cancel(voucher_type, voucher_no):
(now(), frappe.session.user, voucher_type, voucher_no))
def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False):
args.update({"doctype": "Stock Ledger Entry"})
args["doctype"] = "Stock Ledger Entry"
sle = frappe.get_doc(args)
sle.flags.ignore_permissions = 1
sle.allow_negative_stock=allow_negative_stock
sle.via_landed_cost_voucher = via_landed_cost_voucher
sle.insert()
sle.submit()
return sle