Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions api/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ const startServer = async () => {
console.log(`🚀 Environment: ${process.env.NODE_ENV || 'development'}`);
console.log(`🚀 API Endpoint: http://localhost:${PORT}/api`);
console.log('🚀 =====================================');

/*
* THE SHOP'S DECLARED DEFAULT, LOOKING FOR ORDERS NOBODY ANSWERED.
*
* Started here rather than on the till, because a shop served from the
* cloud has no till and its held orders would otherwise sit for ever.
* Does nothing at all until a shop has actually asked for a rule; see
* src/services/unanswered-orders.js.
*/
try {
require('./src/services/unanswered-orders').start();
console.log('✅ Unanswered-order rule running');
} catch (e) {
/* A shop still takes orders without it. Never fatal at boot. */
console.warn('[unanswered-orders] not started:', e && e.message);
}
});

// Handle unhandled promise rejections
Expand Down
30 changes: 30 additions & 0 deletions api/src/controllers/settings.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,36 @@ class SettingController extends BaseController {
data: null,
});
}
/*
* WHEN THE BILL NUMBER STARTS AGAIN, and it is refused rather than
* quietly corrected.
*
* The model coerces an unrecognised value to off, which is the right
* thing for a payload arriving from anywhere. Here, where a person is
* looking at a form, a typo that silently switched a shop's numbering
* off would be found by an accountant in April rather than by whoever
* pressed Save. See utils/bill-number.js.
*/
if (
data.bill_number_reset !== undefined &&
!['', 'off', 'financial', 'calendar'].includes(String(data.bill_number_reset).trim())
) {
return res.status(400).json({
type: 'error',
message: 'Data Not Valid: bill_number_reset must be off, financial or calendar',
data: null,
});
}
if (data.bill_number_fy_start_month !== undefined) {
const month = Number(data.bill_number_fy_start_month);
if (!Number.isInteger(month) || month < 1 || month > 12) {
return res.status(400).json({
type: 'error',
message: 'Data Not Valid: bill_number_fy_start_month must be a month, 1 to 12',
data: null,
});
}
}
if (
data.receiving_prefix !== undefined &&
(String(data.receiving_prefix).length < 1 || String(data.receiving_prefix).length > 6)
Expand Down
45 changes: 44 additions & 1 deletion api/src/helpers/order-attention.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,47 @@ function notifyOrderAttention(details = {}) {
}
}

module.exports = { ATTENTION_EVENT, notifyOrderAttention };
/*
* AND THAT IT NO LONGER DOES.
*
* The alarm repeats until somebody answers, and until now the only thing that
* could stop it was a person pressing something on the page. When the shop's
* own rule decides an order - "auto cancel after ten minutes" - the order IS
* answered, and an alarm still going is an alarm about nothing.
*
* The opposite failure is the one that was built in before this existed: the
* till dropped the order from its pending list the moment its policy spoke,
* without anything acting on it, so the noise stopped while the order sat
* there. A stopped alarm is a promise that something happened, so it is only
* ever sent AFTER the order has actually moved.
*/
const RESOLVED_EVENT = 'posnic:order-resolved';

/**
* Announce that an order no longer needs anybody.
*
* @param {object} details
* @param {string} [details.branchId]
* @param {string} [details.saleId]
* @param {string} [details.state] what it became: accepted or rejected
* @param {string} [details.by] 'rule' when the shop's own default fired
*/
function notifyOrderResolved(details = {}) {
try {
process.emit(RESOLVED_EVENT, {
branchId: details.branchId ? String(details.branchId) : '',
saleId: details.saleId ? String(details.saleId) : '',
state: details.state ? String(details.state) : '',
by: details.by ? String(details.by) : '',
at: new Date().toISOString(),
});
return true;
} catch (e) {
/* A silence that fails to arrive is a shop with a noise it can stop by
hand, which is where it was before. */
console.warn('[order-attention] could not announce a resolution:', e.message);
return false;
}
}

module.exports = { ATTENTION_EVENT, RESOLVED_EVENT, notifyOrderAttention, notifyOrderResolved };
29 changes: 29 additions & 0 deletions api/src/models/branch.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,30 @@ const branchSchema = new Schema(
discount_amount: { type: Number },
discount_percentage: { type: Number },
sales_prefix: { type: String, default: 'S' },
/*
* WHEN THE BILL NUMBER STARTS AGAIN AT ONE, and whether the year is
* printed on it.
*
* Owner: "we need year pattern required in the sales bill number example
* attached have 26 in the year", and on the answer: "i accept
* recommandation and may configurable if people from EU and
* international."
*
* off what every shop does today: one run, for ever
* financial India, CGST Rule 46(b): unique for a FINANCIAL year
* calendar most of the EU, where the year is the calendar one
*
* EMPTY BY DEFAULT, which means off. Ninety shops are mid-year with a
* running series on their invoices; switching them all over on an upgrade
* would change the shape of every bill number overnight and restart the
* count in the middle of a year, which is the one thing an auditor reads
* a series for. A shop turns it on, ideally on the first of its own year.
* See utils/bill-number.js.
*/
bill_number_reset: { type: String, default: '' },
/* Which month a financial year begins in, 1-12. India is April; a shop on
the calendar year sets `bill_number_reset` to `calendar` instead. */
bill_number_fy_start_month: { type: Number, default: 4 },
receiving_prefix: { type: String, default: 'RID' },

smstype: { type: String },
Expand Down Expand Up @@ -312,6 +336,8 @@ class BranchModel {
roundOff: { type: 'String', select: true },
sales_mail: { type: 'String', select: true },
sales_prefix: { type: 'String', select: true },
bill_number_reset: { type: 'String', select: true },
bill_number_fy_start_month: { type: 'Number', select: true },
sales_sms: { type: 'String', select: true },
auto_sms: { type: 'String', select: true },
server_dateformat: { type: 'String', select: true },
Expand Down Expand Up @@ -1070,6 +1096,9 @@ class BranchModel {
discount_amount: 0,
discount_percentage: 0.0,
sales_prefix: 'S',
/* Off, which is what every existing shop does. See the schema. */
bill_number_reset: '',
bill_number_fy_start_month: 4,
receiving_prefix: 'RID',
auto_sms: false, // Default to false or from session settings
sales_sms: false,
Expand Down
28 changes: 28 additions & 0 deletions api/src/models/setting.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,32 @@ class SettingModel extends BaseModel {
discount_percentage: parseFloat(data.discount_percentage),
discount_amount: parseFloat(data.discount_amount),
sales_prefix: data.sales_prefix,
/*
* WHEN THE BILL NUMBER STARTS AGAIN AT ONE.
*
* Only written when the form actually sent it, so a save from an
* older screen - or from any of the other forms that post into this
* same method - cannot silently switch a shop's numbering off. An
* unrecognised value is stored as empty, which is off: a typo must
* not restart a shop's invoice series.
*/
...(data.bill_number_reset !== undefined
? {
bill_number_reset: ['financial', 'calendar'].includes(
String(data.bill_number_reset || '').trim()
)
? String(data.bill_number_reset).trim()
: '',
}
: {}),
...(data.bill_number_fy_start_month !== undefined
? {
bill_number_fy_start_month: (() => {
const month = Number(data.bill_number_fy_start_month);
return Number.isInteger(month) && month >= 1 && month <= 12 ? month : 4;
})(),
}
: {}),
// Shop's own outgoing mail (owner rule: theirs first, ours as the
// cloud fallback). Password stored as given - it must be usable.
...(data.email_smtp_host !== undefined
Expand Down Expand Up @@ -1490,6 +1516,8 @@ class SettingModel extends BaseModel {
discount_percentage: 'discount_percentage',
discount_amount: 'discount_amount',
sales_prefix: 'sales_prefix',
bill_number_reset: 'bill_number_reset',
bill_number_fy_start_month: 'bill_number_fy_start_month',
indian_gst: 'indian_gst',
receiving_prefix: 'receiving_prefix',
branch_gstin_number: 'branch_gstin_number',
Expand Down
Loading
Loading