apple iap-submit: DEVELOPER_REJECTED IAP / subscription / group versions (left by a canceled submission) are submitted as they are, plan says 'add (developer rejected, re-submitted as is)', --new-version only for APPROVED / REJECTED, empty draft reused; fake ASC models cancel and re-add. play build / check: warn before Gradle when defaultConfig does not read -PversionCode / -PversionName, with the exact Groovy / Kotlin line (built with AGP 8.13); android.injected.version.code does not override bundleRelease; post-build versionCode mismatch is a hard stop. play-production-release plan: 'Do you want to restart your review?' dialog, Restart review is never-press. Pitfalls 31-33

This commit is contained in:
CRTkafa 2026-10-12 11:32:05 +08:00
parent 41d2ffcc24
commit 0b1451800c
18 changed files with 399 additions and 26 deletions

View file

@ -163,6 +163,13 @@ export class FakeAsc {
&& !['COMPLETE', 'CANCELING'].includes(String(this.res.get(String(r.rels.reviewSubmission))?.attributes.state)));
if (holder) return json({ errors: [{ status: '409', code: 'STATE_ERROR.ITEM_PART_OF_ANOTHER_SUBMISSION', title: 'The item is already part of another submission',
detail: `${this.res.get(String(vid))?.type ?? kind} with id ${vid} was already added to another reviewSubmission with id ${holder.rels.reviewSubmission}` }] }, 409);
// seen live (2026-10-12): a product version left DEVELOPER_REJECTED by a canceled submission is accepted again as
// it is (201, item READY_FOR_REVIEW); an APPROVED one is not (it needs a new version first)
const v = this.res.get(String(vid));
if (v && /^(inAppPurchase|subscription|subscriptionGroup)Versions$/.test(v.type) && v.attributes.state === 'APPROVED')
return json({ errors: [{ status: '409', code: 'STATE_ERROR', title: 'The request cannot be fulfilled because of the state of another resource.', detail: `${v.type} with id ${v.id} is not in a valid state to be submitted` }] }, 409);
if (v && v.attributes.state === 'DEVELOPER_REJECTED' && v.type !== 'appStoreVersions') v.attributes.state = 'READY_FOR_REVIEW';
main.attributes.state ??= 'READY_FOR_REVIEW';
}
const u = UNIQUE[d.type];
if (u && this.all(d.type).some(r => u(r) === u(main as R)))
@ -269,9 +276,15 @@ export class FakeAsc {
return json({ errors: [{ status: '409', code: 'STATE_ERROR', title: 'The request cannot be fulfilled because of the state of another resource.', detail: 'Version is not ready to be submitted yet, please try again later' }] }, 409);
if (attrs.canceled && self.attributes.state === 'READY_FOR_REVIEW' && !items.length)
return json({ errors: [{ status: '409', code: 'STATE_ERROR', title: 'The request cannot be fulfilled because of the state of another resource.', detail: 'Resource is not in cancellable state' }] }, 409);
// submitting moves the submission (and its items) on; canceling ends it
// submitting moves the submission (and its items) on; canceling ends it, and (seen live) the versions in a canceled
// submission are left DEVELOPER_REJECTED; the app version is editable again (PREPARE_FOR_SUBMISSION)
const wasSubmitted = ['WAITING_FOR_REVIEW', 'IN_REVIEW'].includes(String(self.attributes.state));
self.attributes.state = attrs.submitted ? 'WAITING_FOR_REVIEW' : 'CANCELING';
if (attrs.submitted) for (const it of this.children(self.id, 'reviewSubmissionItems')) it.attributes.state = 'READY_FOR_REVIEW';
if (attrs.canceled && wasSubmitted) for (const it of items) for (const [k, vid] of Object.entries(it.rels)) {
const v = k === 'reviewSubmission' ? undefined : this.res.get(String(vid));
if (v) v.attributes[v.type === 'appStoreVersions' ? 'appVersionState' : 'state'] = v.type === 'appStoreVersions' ? 'PREPARE_FOR_SUBMISSION' : 'DEVELOPER_REJECTED';
}
} else if (self.type === 'reviewSubmissionItems' && attrs.resolved === true) {
// marking a REJECTED item resolved puts it back to READY_FOR_REVIEW (seen live)
if (self.attributes.state === 'REJECTED') self.attributes.state = 'READY_FOR_REVIEW';